https://github.com/libsdl-org/SDL_image/commit/495e7d0f60d8770e41ddb6e6f465233f725d59e1
From 495e7d0f60d8770e41ddb6e6f465233f725d59e1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 8 Jan 2025 11:27:51 -0800
Subject: [PATCH] Handle webp animation composition
Fixes https://github.com/libsdl-org/SDL_image/issues/500
(cherry picked from commit 9f115a1e1dc935ddd8fd64abb0a59b588c8b2e2a)
(cherry picked from commit 6bfa1d39a04726dd97ae28a201680d68422ce65c)
---
src/IMG_webp.c | 160 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 106 insertions(+), 54 deletions(-)
diff --git a/src/IMG_webp.c b/src/IMG_webp.c
index df30eb10..a6254f87 100644
--- a/src/IMG_webp.c
+++ b/src/IMG_webp.c
@@ -50,7 +50,9 @@ static struct {
uint8_t* (*WebPDecodeRGBInto) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride);
uint8_t* (*WebPDecodeRGBAInto) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride);
WebPDemuxer* (*WebPDemuxInternal)(const WebPData* data, int allow_partial, WebPDemuxState* state, int version);
- int (*WebPDemuxGetFrame)(const WebPDemuxer* dmux, int frame_number, WebPIterator* iter);
+ int (*WebPDemuxGetFrame)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter);
+ int (*WebPDemuxNextFrame)(WebPIterator *iter);
+ void (*WebPDemuxReleaseIterator)(WebPIterator *iter);
uint32_t (*WebPDemuxGetI)(const WebPDemuxer* dmux, WebPFormatFeature feature);
void (*WebPDemuxDelete)(WebPDemuxer* dmux);
} lib;
@@ -92,8 +94,10 @@ int IMG_InitWEBP()
FUNCTION_LOADER_LIBWEBP(WebPDecodeRGBInto, uint8_t * (*) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride))
FUNCTION_LOADER_LIBWEBP(WebPDecodeRGBAInto, uint8_t * (*) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride))
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxInternal, WebPDemuxer* (*)(const WebPData*, int, WebPDemuxState*, int))
- FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetFrame, int (*)(const WebPDemuxer* dmux, int frame_number, WebPIterator* iter))
- FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetI, uint32_t (*)(const WebPDemuxer* dmux, WebPFormatFeature feature));
+ FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetFrame, int (*)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter))
+ FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxNextFrame, int (*)(WebPIterator *iter))
+ FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxReleaseIterator, void (*)(WebPIterator *iter))
+ FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetI, uint32_t (*)(const WebPDemuxer* dmux, WebPFormatFeature feature))
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxDelete, void (*)(WebPDemuxer* dmux))
}
++lib.loaded;
@@ -259,16 +263,16 @@ IMG_Animation *IMG_LoadWEBPAnimation_RW(SDL_RWops *src)
{
Sint64 start;
const char *error = NULL;
- Uint32 format;
WebPBitstreamFeatures features;
- struct WebPDemuxer* dmuxer = NULL;
+ struct WebPDemuxer *demuxer = NULL;
WebPIterator iter;
IMG_Animation *anim = NULL;
int raw_data_size;
uint8_t *raw_data = NULL;
- uint8_t *ret;
- int frame_idx;
WebPData wd;
+ uint32_t bgcolor;
+ SDL_Surface *canvas = NULL;
+ WebPMuxAnimDispose dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
if (!src) {
/* The error message has been set in SDL_RWFromFile */
@@ -289,79 +293,127 @@ IMG_Animation *IMG_LoadWEBPAnimation_RW(SDL_RWops *src)
raw_data = (uint8_t*) SDL_malloc(raw_data_size);
if (raw_data == NULL) {
- error = "Failed to allocate enough buffer for WEBP Animation";
goto error;
}
if ((int)SDL_RWread(src, raw_data, 1, raw_data_size) != raw_data_size) {
- error = "Failed to read WEBP Animation";
goto error;
}
if (lib.WebPGetFeaturesInternal(raw_data, raw_data_size, &features, WEBP_DECODER_ABI_VERSION) != VP8_STATUS_OK) {
- error = "WebPGetFeatures has failed";
+ error = "WebPGetFeatures() failed";
goto error;
}
- if (features.has_alpha) {
- format = SDL_PIXELFORMAT_RGBA32;
- } else {
- format = SDL_PIXELFORMAT_RGB24;
- }
-
wd.size = raw_data_size;
wd.bytes = raw_data;
- dmuxer = lib.WebPDemuxInternal(&wd, 0, NULL, WEBP_DEMUX_ABI_VERSION);
- anim = (IMG_Animation *)SDL_malloc(sizeof(IMG_Animation));
+ demuxer = lib.WebPDemuxInternal(&wd, 0, NULL, WEBP_DEMUX_ABI_VERSION);
+ if (!demuxer) {
+ error = "WebPDemux() failed";
+ goto error;
+ }
+
+ anim = (IMG_Animation *)SDL_calloc(1, sizeof(*anim));
+ if (!anim) {
+ goto error;
+ }
anim->w = features.width;
anim->h = features.height;
- anim->count = lib.WebPDemuxGetI(dmuxer, WEBP_FF_FRAME_COUNT);
+ anim->count = lib.WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
anim->frames = (SDL_Surface **)SDL_calloc(anim->count, sizeof(*anim->frames));
anim->delays = (int *)SDL_calloc(anim->count, sizeof(*anim->delays));
- for (frame_idx = 0; frame_idx < (anim->count); frame_idx++) {
- SDL_Surface* curr;
- if (lib.WebPDemuxGetFrame(dmuxer, frame_idx, &iter) == 0) {
- break;
- }
- curr = SDL_CreateRGBSurfaceWithFormat(0, features.width, features.height, 0, format);
- if (curr == NULL) {
- error = "Failed to allocate SDL_Surface";
- goto error;
- }
- anim->frames[frame_idx] = curr;
- anim->delays[frame_idx] = iter.duration;
- if (features.has_alpha) {
- ret = lib.WebPDecodeRGBAInto(
- iter.fragment.bytes,
- iter.fragment.size,
- (uint8_t *)curr->pixels,
- curr->pitch * curr->h,
- curr->pitch);
- } else {
- ret = lib.WebPDecodeRGBInto(
- iter.fragment.bytes, iter.fragment.size,
- (uint8_t *)curr->pixels,
- curr->pitch * curr->h,
- curr->pitch);
- }
- if (ret == NULL) {
- break;
- }
+ if (!anim->frames || !anim->delays) {
+ goto error;
}
- if (dmuxer) {
- lib.WebPDemuxDelete(dmuxer);
+
+ canvas = SDL_CreateRGBSurfaceWithFormat(0, anim->w, anim->h, 0, features.has_alpha ? SDL_PIXELFORMAT_RGBA32 : SDL_PIXELFORMAT_RGBX32);
+ if (!canvas) {
+ goto error;
}
- if (raw_data) {
- SDL_free(raw_data);
+ /* Background color is BGRA byte order according to the spec */
+ bgcolor = lib.WebPDemuxGetI(demuxer, WEBP_FF_BACKGROUND_COLOR);
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ bgcolor = SDL_MapRGBA(canvas->format,
+ (bgcolor >> 8) & 0xFF,
+ (bgcolor >> 16) & 0xFF,
+ (bgcolor >> 24) & 0xFF,
+ (bgcolor >> 0) & 0xFF);
+#else
+ bgcolor = SDL_MapRGBA(canvas->format,
+ (bgcolor >> 16) & 0xFF,
+ (bgcolor >> 8) & 0xFF,
+ (bgcolor >> 0) & 0xFF,
+ (bgcolor >> 24) & 0xFF);
+#endif
+
+ SDL_zero(iter);
+ if (lib.WebPDemuxGetFrame(demuxer, 1, &iter)) {
+ do {
+ SDL_Surface* curr;
+ SDL_Rect dst;
+ int frame_idx = (iter.frame_num - 1);
+ if (frame_idx < 0 || frame_idx >= anim->count) {
+ continue;
+ }
+
+ if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
+ SDL_FillRect(canvas, NULL, bgcolor);
+ }
+
+ curr = SDL_CreateRGBSurfaceWithFormat(0, iter.width, iter.height, 0, SDL_PIXELFORMAT_RGBA32);
+ if (!curr) {
+ goto error;
+ }
+
+ if (!lib.WebPDecodeRGBAInto(iter.fragment.bytes,
+ iter.fragment.size,
+ (uint8_t*)curr->pixels,
+ curr->pitch * curr->h,
+ curr->pitch)) {
+ error = "WebPDecodeRGBAInto() failed";
+ SDL_FreeSurface(curr);
+ goto error;
+ }
+
+ if (iter.blend_method == WEBP_MUX_BLEND) {
+ SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_BLEND);
+ } else {
+ SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_NONE);
+ }
+ dst.x = iter.x_offset;
+ dst.y = iter.y_offset;
+ dst.w = iter.width;
+ dst.h = iter.height;
+ SDL_BlitSurface(curr, NULL, canvas, &dst);
+ SDL_FreeSurface(curr);
+
+ anim->frames[frame_idx] = SDL_DuplicateSurface(canvas);
+ anim->delays[frame_idx] = iter.duration;
+ dispose_method = iter.dispose_method;
+
+ } while (lib.WebPDemuxNextFrame(&iter));
+
+ lib.WebPDemuxReleaseIterator(&iter);
}
+
+ SDL_FreeSurface(canvas);
+
+ lib.WebPDemuxDelete(demuxer);
+
+ SDL_free(raw_data);
+
return anim;
+
error:
+ if (canvas) {
+ SDL_FreeSurface(canvas);
+ }
if (anim) {
IMG_FreeAnimation(anim);
}
- if (dmuxer) {
- lib.WebPDemuxDelete(dmuxer);
+ if (demuxer) {
+ lib.WebPDemuxDelete(demuxer);
}
if (raw_data) {
SDL_free(raw_data);