Merge changes I9b4dc36c,I4e0eef4d

* changes:
  Mux: support parsing unknown chunks within a frame/fragment.
  Stricter check for presence of alpha when writing lossless images
This commit is contained in:
James Zern 2013-07-22 17:05:27 -07:00 committed by Gerrit Code Review
commit 11249abfc7
5 changed files with 124 additions and 120 deletions

View File

@ -497,8 +497,10 @@ static int WriteWebPWithMetadata(FILE* const out,
webp_size -= kVP8XChunkSize;
} else {
const int is_lossless = !memcmp(webp, "VP8L", kTagSize);
// The alpha flag is forced with lossless images.
if (is_lossless) flags |= kAlphaFlag;
if (is_lossless) {
// Presence of alpha is stored in the 29th bit of VP8L data.
if (webp[kChunkHeaderSize + 3] & (1 << 5)) flags |= kAlphaFlag;
}
ok = ok && (fwrite(kVP8XHeader, kChunkHeaderSize, 1, out) == 1);
ok = ok && WriteLE32(out, flags);
ok = ok && WriteLE24(out, picture->width - 1);

View File

@ -39,12 +39,6 @@ WebPMux* WebPNewInternal(int version) {
}
}
static void DeleteAllChunks(WebPChunk** const chunk_list) {
while (*chunk_list) {
*chunk_list = ChunkDelete(*chunk_list);
}
}
// Delete all images in 'wpi_list'.
static void DeleteAllImages(WebPMuxImage** const wpi_list) {
while (*wpi_list != NULL) {
@ -55,12 +49,12 @@ static void DeleteAllImages(WebPMuxImage** const wpi_list) {
static void MuxRelease(WebPMux* const mux) {
if (mux == NULL) return;
DeleteAllImages(&mux->images_);
DeleteAllChunks(&mux->vp8x_);
DeleteAllChunks(&mux->iccp_);
DeleteAllChunks(&mux->anim_);
DeleteAllChunks(&mux->exif_);
DeleteAllChunks(&mux->xmp_);
DeleteAllChunks(&mux->unknown_);
ChunkListDelete(&mux->vp8x_);
ChunkListDelete(&mux->iccp_);
ChunkListDelete(&mux->anim_);
ChunkListDelete(&mux->exif_);
ChunkListDelete(&mux->xmp_);
ChunkListDelete(&mux->unknown_);
}
void WebPMuxDelete(WebPMux* mux) {
@ -103,34 +97,26 @@ static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth,
// Create data for frame/fragment given image data, offsets and duration.
static WebPMuxError CreateFrameFragmentData(
const WebPData* const image, int x_offset, int y_offset, int duration,
WebPMuxAnimDispose dispose_method, int is_lossless, int is_frame,
int width, int height, const WebPMuxFrameInfo* const info, int is_frame,
WebPData* const frame_frgm) {
int width;
int height;
uint8_t* frame_frgm_bytes;
const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size;
const int ok = is_lossless ?
VP8LGetInfo(image->bytes, image->size, &width, &height, NULL) :
VP8GetInfo(image->bytes, image->size, image->size, &width, &height);
if (!ok) return WEBP_MUX_INVALID_ARGUMENT;
assert(width > 0 && height > 0 && duration >= 0);
assert(dispose_method == (dispose_method & 1));
assert(width > 0 && height > 0 && info->duration >= 0);
assert(info->dispose_method == (info->dispose_method & 1));
// Note: assertion on upper bounds is done in PutLE24().
frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size);
if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
PutLE24(frame_frgm_bytes + 0, x_offset / 2);
PutLE24(frame_frgm_bytes + 3, y_offset / 2);
PutLE24(frame_frgm_bytes + 0, info->x_offset / 2);
PutLE24(frame_frgm_bytes + 3, info->y_offset / 2);
if (is_frame) {
PutLE24(frame_frgm_bytes + 6, width - 1);
PutLE24(frame_frgm_bytes + 9, height - 1);
PutLE24(frame_frgm_bytes + 12, duration);
frame_frgm_bytes[15] = (dispose_method & 1);
PutLE24(frame_frgm_bytes + 12, info->duration);
frame_frgm_bytes[15] = (info->dispose_method & 1);
}
frame_frgm->bytes = frame_frgm_bytes;
@ -241,7 +227,9 @@ static WebPMuxError SetAlphaAndImageChunks(
&wpi->alpha_);
if (err != WEBP_MUX_OK) return err;
}
return AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
if (err != WEBP_MUX_OK) return err;
return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;
}
WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
@ -315,24 +303,24 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful.
{
const int is_lossless = (wpi.img_->tag_ == kChunks[IDX_VP8L].tag);
const int x_offset = frame->x_offset & ~1; // Snap offsets to even.
const int y_offset = frame->y_offset & ~1;
const int duration = is_frame ? frame->duration : 1 /* unused */;
const WebPMuxAnimDispose dispose_method =
is_frame ? frame->dispose_method : 0 /* unused */;
const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag;
WebPData frame_frgm;
if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET ||
y_offset < 0 || y_offset >= MAX_POSITION_OFFSET ||
(duration < 0 || duration >= MAX_DURATION) ||
dispose_method != (dispose_method & 1)) {
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 = 0;
}
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.img_->data_, x_offset, y_offset,
duration, dispose_method, is_lossless,
is_frame, &frame_frgm);
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_);
@ -407,34 +395,10 @@ static WebPMuxError GetFrameFragmentInfo(
return WEBP_MUX_OK;
}
WebPMuxError MuxGetImageInfo(const WebPChunk* const image_chunk,
int* const width, int* const height,
int* const has_alpha) {
const uint32_t tag = image_chunk->tag_;
const WebPData* const data = &image_chunk->data_;
int w, h;
int a = 0;
int ok;
assert(image_chunk != NULL);
assert(tag == kChunks[IDX_VP8].tag || tag == kChunks[IDX_VP8L].tag);
ok = (tag == kChunks[IDX_VP8].tag) ?
VP8GetInfo(data->bytes, data->size, data->size, &w, &h) :
VP8LGetInfo(data->bytes, data->size, &w, &h, &a);
if (ok) {
if (width != NULL) *width = w;
if (height != NULL) *height = h;
if (has_alpha != NULL) *has_alpha = a;
return WEBP_MUX_OK;
} else {
return WEBP_MUX_BAD_DATA;
}
}
static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
int* const x_offset, int* const y_offset,
int* const duration,
int* const width, int* const height) {
const WebPChunk* const image_chunk = wpi->img_;
const WebPChunk* const frame_frgm_chunk = wpi->header_;
// Get offsets and duration from ANMF/FRGM chunk.
@ -443,7 +407,9 @@ static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
if (err != WEBP_MUX_OK) return err;
// Get width and height from VP8/VP8L chunk.
return MuxGetImageInfo(image_chunk, width, height, NULL);
if (width != NULL) *width = wpi->width_;
if (height != NULL) *height = wpi->height_;
return 1;
}
static WebPMuxError GetImageCanvasWidthHeight(
@ -488,13 +454,9 @@ static WebPMuxError GetImageCanvasWidthHeight(
return WEBP_MUX_INVALID_ARGUMENT;
}
} else {
// For a single image, extract the width & height from VP8/VP8L image-data.
int w, h;
const WebPChunk* const image_chunk = wpi->img_;
const WebPMuxError err = MuxGetImageInfo(image_chunk, &w, &h, NULL);
if (err != WEBP_MUX_OK) return err;
*width = w;
*height = h;
// For a single image, canvas dimensions are same as image dimensions.
*width = wpi->width_;
*height = wpi->height_;
}
return WEBP_MUX_OK;
}
@ -563,9 +525,8 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
return WEBP_MUX_INVALID_ARGUMENT;
}
if (MuxHasLosslessImages(images)) {
// We have a file with a VP8X chunk having some lossless images.
// As lossless images implicitly contain alpha, force ALPHA_FLAG to be true.
if (MuxHasAlpha(images)) {
// This means some frames explicitly/implicitly contain alpha.
// Note: This 'flags' update must NOT be done for a lossless image
// without a VP8X chunk!
flags |= ALPHA_FLAG;
@ -614,16 +575,6 @@ static WebPMuxError MuxCleanup(WebPMux* const mux) {
return WEBP_MUX_OK;
}
// Total size of a list of chunks.
static size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
size_t size = 0;
while (chunk_list != NULL) {
size += ChunkDiskSize(chunk_list);
chunk_list = chunk_list->next_;
}
return size;
}
// Total size of a list of images.
static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {
size_t size = 0;

View File

@ -48,6 +48,10 @@ struct WebPMuxImage {
WebPChunk* header_; // Corresponds to WEBP_CHUNK_ANMF/WEBP_CHUNK_FRGM.
WebPChunk* alpha_; // Corresponds to WEBP_CHUNK_ALPHA.
WebPChunk* img_; // Corresponds to WEBP_CHUNK_IMAGE.
WebPChunk* unknown_; // Corresponds to WEBP_CHUNK_UNKNOWN.
int width_;
int height_;
int has_alpha_; // Through ALPH chunk or as part of VP8L.
int is_partial_; // True if only some of the chunks are filled.
WebPMuxImage* next_;
};
@ -133,6 +137,9 @@ WebPChunk* ChunkRelease(WebPChunk* const chunk);
// Deletes given chunk & returns chunk->next_.
WebPChunk* ChunkDelete(WebPChunk* const chunk);
// Deletes all chunks in the given chunk list.
void ChunkListDelete(WebPChunk** const chunk_list);
// Returns size of the chunk including chunk header and padding byte (if any).
static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) {
return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U);
@ -145,14 +152,12 @@ static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) {
return SizeWithPadding(data_size);
}
// Total size of a list of chunks.
size_t ChunkListDiskSize(const WebPChunk* chunk_list);
// Write out the given list of chunks into 'dst'.
uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst);
// Get the width, height and has_alpha info of image stored in 'image_chunk'.
WebPMuxError MuxGetImageInfo(const WebPChunk* const image_chunk,
int* const width, int* const height,
int* const has_alpha);
//------------------------------------------------------------------------------
// MuxImage object management.
@ -170,6 +175,10 @@ WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi);
// If id == WEBP_CHUNK_NIL, all images will be matched.
int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id);
// Update width/height/has_alpha info from chunks within wpi.
// Also remove ALPH chunk if not needed.
int MuxImageFinalize(WebPMuxImage* const wpi);
// Check if given ID corresponds to an image related chunk.
static WEBP_INLINE int IsWPI(WebPChunkId id) {
switch (id) {
@ -200,8 +209,8 @@ uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst);
//------------------------------------------------------------------------------
// Helper methods for mux.
// Checks if the given image list contains at least one lossless image.
int MuxHasLosslessImages(const WebPMuxImage* images);
// Checks if the given image list contains at least one image with alpha.
int MuxHasAlpha(const WebPMuxImage* images);
// Write out RIFF header into 'data', given total data size 'size'.
uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size);

View File

@ -187,6 +187,12 @@ WebPChunk* ChunkDelete(WebPChunk* const chunk) {
return next;
}
void ChunkListDelete(WebPChunk** const chunk_list) {
while (*chunk_list != NULL) {
*chunk_list = ChunkDelete(*chunk_list);
}
}
//------------------------------------------------------------------------------
// Chunk serialization methods.
@ -211,6 +217,15 @@ uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
return dst;
}
size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
size_t size = 0;
while (chunk_list != NULL) {
size += ChunkDiskSize(chunk_list);
chunk_list = chunk_list->next_;
}
return size;
}
//------------------------------------------------------------------------------
// Life of a MuxImage object.
@ -225,6 +240,7 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
ChunkDelete(wpi->header_);
ChunkDelete(wpi->alpha_);
ChunkDelete(wpi->img_);
ChunkListDelete(&wpi->unknown_);
next = wpi->next_;
MuxImageInit(wpi);
@ -356,6 +372,7 @@ size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
return size;
}
@ -387,18 +404,16 @@ uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
}
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
return dst;
}
//------------------------------------------------------------------------------
// Helper methods for mux.
int MuxHasLosslessImages(const WebPMuxImage* images) {
int MuxHasAlpha(const WebPMuxImage* images) {
while (images != NULL) {
assert(images->img_ != NULL);
if (images->img_->tag_ == kChunks[IDX_VP8L].tag) {
return 1;
}
if (images->has_alpha_) return 1;
images = images->next_;
}
return 0;
@ -512,14 +527,18 @@ WebPMuxError MuxValidate(const WebPMux* const mux) {
if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
// ALPHA_FLAG & alpha chunk(s) are consistent.
if (MuxHasLosslessImages(mux->images_)) {
if (MuxHasAlpha(mux->images_)) {
if (num_vp8x > 0) {
// Special case: we have a VP8X chunk as well as some lossless images.
// VP8X chunk is present, so it should contain ALPHA_FLAG.
if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;
}
} else {
err = ValidateChunk(mux, IDX_ALPHA, ALPHA_FLAG, flags, -1, &num_alpha);
} else {
// VP8X chunk is not present, so ALPH chunks should NOT be present either.
err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);
if (err != WEBP_MUX_OK) return err;
if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;
}
} else { // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present.
if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT;
}
// num_fragments & num_images are consistent.

View File

@ -76,6 +76,29 @@ static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
}
int MuxImageFinalize(WebPMuxImage* const wpi) {
const WebPChunk* const img = wpi->img_;
const WebPData* const image = &img->data_;
const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag);
int w, h;
int vp8l_has_alpha = 0;
const int ok = is_lossless ?
VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :
VP8GetInfo(image->bytes, image->size, image->size, &w, &h);
assert(img != NULL);
if (ok) {
// Ignore ALPH chunk accompanying VP8L.
if (is_lossless && (wpi->alpha_ != NULL)) {
ChunkDelete(wpi->alpha_);
wpi->alpha_ = NULL;
}
wpi->width_ = w;
wpi->height_ = h;
wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL);
}
return ok;
}
static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
WebPMuxImage* const wpi) {
const uint8_t* bytes = chunk->data_.bytes;
@ -121,8 +144,14 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
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;
@ -218,6 +247,7 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
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.
@ -291,16 +321,17 @@ static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
// 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);
f = GetLE32(data.bytes + 0);
w = GetLE24(data.bytes + 4) + 1;
h = GetLE24(data.bytes + 7) + 1;
} else { // Single image case.
int has_alpha;
const WebPMuxImage* const wpi = mux->images_;
WebPMuxError err = ValidateForSingleImage(mux);
if (err != WEBP_MUX_OK) return err;
err = MuxGetImageInfo(mux->images_->img_, &w, &h, &has_alpha);
if (err != WEBP_MUX_OK) return err;
if (has_alpha) f |= ALPHA_FLAG;
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;
@ -355,15 +386,7 @@ static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
dst = MuxEmitRiffHeader(data, size);
if (need_vp8x) {
int w, h;
WebPMuxError err;
assert(wpi->img_ != NULL);
err = MuxGetImageInfo(wpi->img_, &w, &h, NULL);
if (err != WEBP_MUX_OK) {
free(data);
return err;
}
dst = EmitVP8XChunk(dst, w, h, ALPHA_FLAG); // VP8X.
dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X.
dst = ChunkListEmit(wpi->alpha_, dst); // ALPH.
}