From 8ba1bf61a0b3637dbc3e16b2cb0707b8f9892f04 Mon Sep 17 00:00:00 2001 From: Urvang Joshi Date: Fri, 19 Jul 2013 11:55:09 -0700 Subject: [PATCH] Stricter check for presence of alpha when writing lossless images Earlier, all lossless images were assumed to contain alpha. Now, we use the 'alpha_is_used' bit from the VP8L bitstream to determine the same. Detecting an absence of alpha can sometimes lead to much more efficient rendering, especially for animated images. Related: refine mux code to read width/height/has_alpha information only once per frame/fragment. This avoid frequent calls to VP8(L)GetInfo(). Change-Id: I4e0eef4db7d94425396c7dff6ca5599d5bca8297 --- examples/cwebp.c | 6 ++- src/mux/muxedit.c | 97 ++++++++++++++----------------------------- src/mux/muxi.h | 16 +++---- src/mux/muxinternal.c | 21 +++++----- src/mux/muxread.c | 46 +++++++++++++------- 5 files changed, 88 insertions(+), 98 deletions(-) diff --git a/examples/cwebp.c b/examples/cwebp.c index efdeccfe..cfbb47aa 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -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); diff --git a/src/mux/muxedit.c b/src/mux/muxedit.c index 1c0d2edb..105db013 100644 --- a/src/mux/muxedit.c +++ b/src/mux/muxedit.c @@ -103,34 +103,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; @@ -243,7 +235,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, @@ -317,24 +311,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_); @@ -409,34 +403,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. @@ -445,7 +415,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( @@ -490,13 +462,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; } @@ -565,9 +533,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; diff --git a/src/mux/muxi.h b/src/mux/muxi.h index e9750bc6..20c12e8f 100644 --- a/src/mux/muxi.h +++ b/src/mux/muxi.h @@ -48,6 +48,9 @@ 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. + 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_; }; @@ -148,11 +151,6 @@ static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) { // 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 +168,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 +202,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); diff --git a/src/mux/muxinternal.c b/src/mux/muxinternal.c index 03b8af1f..56b2fcfe 100644 --- a/src/mux/muxinternal.c +++ b/src/mux/muxinternal.c @@ -393,12 +393,9 @@ uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* 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 +509,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. diff --git a/src/mux/muxread.c b/src/mux/muxread.c index cdeba939..a30508e3 100644 --- a/src/mux/muxread.c +++ b/src/mux/muxread.c @@ -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,6 +144,7 @@ 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; default: @@ -218,6 +242,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 +316,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 +381,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. }