mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-27 06:08:21 +01:00
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
This commit is contained in:
parent
a03c3516cb
commit
8ba1bf61a0
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user