diff --git a/examples/gif2webp.c b/examples/gif2webp.c index eeed97b4..060e466d 100644 --- a/examples/gif2webp.c +++ b/examples/gif2webp.c @@ -442,14 +442,21 @@ int main(int argc, const char *argv[]) { gif->SWidth, gif->SHeight); } } - // Allocate current buffer + // Set definitive canvas size. + err = WebPMuxSetCanvasSize(mux, gif->SWidth, gif->SHeight); + if (err != WEBP_MUX_OK) { + fprintf(stderr, "Invalid canvas size %d x %d\n", + gif->SWidth, gif->SHeight); + goto End; + } + // Allocate current buffer. frame.width = gif->SWidth; frame.height = gif->SHeight; frame.use_argb = 1; if (!WebPPictureAlloc(&frame)) goto End; WebPUtilClearPic(&frame, NULL); - // Initialize cache + // Initialize cache. cache = WebPFrameCacheNew(frame.width, frame.height, kmin, kmax, allow_mixed); if (cache == NULL) goto End; diff --git a/src/mux/muxedit.c b/src/mux/muxedit.c index 2c6c5f9a..26b19249 100644 --- a/src/mux/muxedit.c +++ b/src/mux/muxedit.c @@ -20,8 +20,10 @@ // Life of a mux object. static void MuxInit(WebPMux* const mux) { - if (mux == NULL) return; + assert(mux != NULL); memset(mux, 0, sizeof(*mux)); + mux->canvas_width_ = 0; // just to be explicit + mux->canvas_height_ = 0; } WebPMux* WebPNewInternal(int version) { @@ -29,8 +31,7 @@ WebPMux* WebPNewInternal(int version) { return NULL; } else { WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux)); - // If mux is NULL MuxInit is a noop. - MuxInit(mux); + if (mux != NULL) MuxInit(mux); return mux; } } @@ -43,7 +44,7 @@ static void DeleteAllImages(WebPMuxImage** const wpi_list) { } static void MuxRelease(WebPMux* const mux) { - if (mux == NULL) return; + assert(mux != NULL); DeleteAllImages(&mux->images_); ChunkListDelete(&mux->vp8x_); ChunkListDelete(&mux->iccp_); @@ -54,9 +55,10 @@ static void MuxRelease(WebPMux* const mux) { } void WebPMuxDelete(WebPMux* mux) { - // If mux is NULL MuxRelease is a noop. - MuxRelease(mux); - WebPSafeFree(mux); + if (mux != NULL) { + MuxRelease(mux); + WebPSafeFree(mux); + } } //------------------------------------------------------------------------------ @@ -360,6 +362,32 @@ WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux, return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1); } +WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height) { + WebPMuxError err; + if (mux == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width < 0 || height < 0 || + width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width * (uint64_t)height >= MAX_IMAGE_AREA) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if ((width * height) == 0 && (width | height) != 0) { + // one of width / height is zero, but not both -> invalid! + return WEBP_MUX_INVALID_ARGUMENT; + } + // If we already assembled a VP8X chunk, invalidate it. + err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + + mux->canvas_width_ = width; + mux->canvas_height_ = height; + return WEBP_MUX_OK; +} + //------------------------------------------------------------------------------ // Delete API(s). @@ -413,9 +441,10 @@ static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi, return WEBP_MUX_OK; } -static WebPMuxError GetImageCanvasWidthHeight( - const WebPMux* const mux, uint32_t flags, - int* const width, int* const height) { +// Returns the tightest dimension for the canvas considering the image list. +static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux, + uint32_t flags, + int* const width, int* const height) { WebPMuxImage* wpi = NULL; assert(mux != NULL); assert(width != NULL && height != NULL); @@ -513,12 +542,7 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { flags |= ALPHA_FLAG; // Some images have an alpha channel. } - if (flags == 0) { - // For Simple Image, VP8X chunk should not be added. - return WEBP_MUX_OK; - } - - err = GetImageCanvasWidthHeight(mux, flags, &width, &height); + err = GetAdjustedCanvasSize(mux, flags, &width, &height); if (err != WEBP_MUX_OK) return err; if (width <= 0 || height <= 0) { @@ -528,6 +552,19 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { return WEBP_MUX_INVALID_ARGUMENT; } + if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) { + if (width > mux->canvas_width_ || height > mux->canvas_height_) { + return WEBP_MUX_INVALID_ARGUMENT; + } + width = mux->canvas_width_; + height = mux->canvas_height_; + } + + if (flags == 0) { + // For Simple Image, VP8X chunk should not be added. + return WEBP_MUX_OK; + } + if (MuxHasAlpha(images)) { // This means some frames explicitly/implicitly contain alpha. // Note: This 'flags' update must NOT be done for a lossless image @@ -603,7 +640,13 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { uint8_t* dst = NULL; WebPMuxError err; - if (mux == NULL || assembled_data == NULL) { + if (assembled_data == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + // Clean up returned data, in case something goes wrong. + memset(assembled_data, 0, sizeof(*assembled_data)); + + if (mux == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } @@ -649,4 +692,3 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { } //------------------------------------------------------------------------------ - diff --git a/src/mux/muxi.h b/src/mux/muxi.h index 277d5fba..6a410232 100644 --- a/src/mux/muxi.h +++ b/src/mux/muxi.h @@ -65,7 +65,9 @@ struct WebPMux { WebPChunk* anim_; WebPChunk* vp8x_; - WebPChunk* unknown_; + WebPChunk* unknown_; + int canvas_width_; + int canvas_height_; }; // CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only. diff --git a/src/mux/muxread.c b/src/mux/muxread.c index db51cf12..bba09a5d 100644 --- a/src/mux/muxread.c +++ b/src/mux/muxread.c @@ -264,6 +264,10 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, // 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; @@ -320,14 +324,20 @@ static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, f = GetLE32(data.bytes + 0); w = GetLE24(data.bytes + 4) + 1; h = GetLE24(data.bytes + 7) + 1; - } else { // Single image case. + } else { const WebPMuxImage* const wpi = mux->images_; - WebPMuxError err = ValidateForSingleImage(mux); - if (err != WEBP_MUX_OK) return err; - assert(wpi != NULL); - w = wpi->width_; - h = wpi->height_; - if (wpi->has_alpha_) f |= ALPHA_FLAG; + // Grab user-forced canvas size as default. + w = mux->canvas_width_; + h = mux->canvas_height_; + if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) { + // single image and not forced canvas size => use dimension of first frame + assert(wpi != NULL); + w = wpi->width_; + h = wpi->height_; + } + if (wpi != NULL) { + if (wpi->has_alpha_) f |= ALPHA_FLAG; + } } if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; @@ -537,4 +547,3 @@ WebPMuxError WebPMuxNumChunks(const WebPMux* mux, } //------------------------------------------------------------------------------ - diff --git a/src/webp/mux.h b/src/webp/mux.h index eb57f51c..578d9e02 100644 --- a/src/webp/mux.h +++ b/src/webp/mux.h @@ -55,7 +55,7 @@ extern "C" { #endif -#define WEBP_MUX_ABI_VERSION 0x0101 // MAJOR(8b) + MINOR(8b) +#define WEBP_MUX_ABI_VERSION 0x0102 // MAJOR(8b) + MINOR(8b) // Note: forward declaring enumerations is not allowed in (strict) C and C++, // the types are left here for reference. @@ -105,6 +105,7 @@ WEBP_EXTERN(WebPMux*) WebPNewInternal(int); // Creates an empty mux object. // Returns: // A pointer to the newly created empty mux object. +// Or NULL in case of memory error. static WEBP_INLINE WebPMux* WebPMuxNew(void) { return WebPNewInternal(WEBP_MUX_ABI_VERSION); } @@ -309,6 +310,24 @@ WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams( //------------------------------------------------------------------------------ // Misc Utilities. +// Sets the canvas size for the mux object. The width and height can be +// specified explicitly or left as zero (0, 0). +// * When width and height are specified explicitly, then this frame bound is +// enforced during subsequent calls to WebPMuxAssemble() and an error is +// reported if any animated frame does not completely fit within the canvas. +// * When unspecified (0, 0), the constructed canvas will get the frame bounds +// from the bounding-box over all frames after calling WebPMuxAssemble(). +// Parameters: +// mux - (in) object to which the canvas size is to be set +// width - (in) canvas width +// height - (in) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or +// width or height are invalid or out of bounds +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height); + // Gets the canvas size from the mux object. // Note: This method assumes that the VP8X chunk, if present, is up-to-date. // That is, the mux object hasn't been modified since the last call to @@ -356,7 +375,8 @@ WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux, // Note: The content of 'assembled_data' will be ignored and overwritten. // Also, the content of 'assembled_data' is allocated using malloc(), and NOT // owned by the 'mux' object. It MUST be deallocated by the caller by calling -// WebPDataClear(). +// WebPDataClear(). It's always safe to call WebPDataClear() upon return, +// even in case of error. // Parameters: // mux - (in/out) object whose chunks are to be assembled // assembled_data - (out) assembled WebP data