Skip to content

Commit 9f115a1

Browse files
committed
Handle webp animation composition
Fixes libsdl-org#500
1 parent 479b4ef commit 9f115a1

File tree

1 file changed

+98
-54
lines changed

1 file changed

+98
-54
lines changed

src/IMG_webp.c

Lines changed: 98 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ static struct {
5050
uint8_t* (*WebPDecodeRGBInto) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride);
5151
uint8_t* (*WebPDecodeRGBAInto) (const uint8_t* data, size_t data_size, uint8_t* output_buffer, size_t output_buffer_size, int output_stride);
5252
WebPDemuxer* (*WebPDemuxInternal)(const WebPData* data, int allow_partial, WebPDemuxState* state, int version);
53-
int (*WebPDemuxGetFrame)(const WebPDemuxer* dmux, int frame_number, WebPIterator* iter);
53+
int (*WebPDemuxGetFrame)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter);
54+
int (*WebPDemuxNextFrame)(WebPIterator *iter);
55+
void (*WebPDemuxReleaseIterator)(WebPIterator *iter);
5456
uint32_t (*WebPDemuxGetI)(const WebPDemuxer* dmux, WebPFormatFeature feature);
5557
void (*WebPDemuxDelete)(WebPDemuxer* dmux);
5658
} lib;
@@ -92,7 +94,9 @@ static bool IMG_InitWEBP(void)
9294
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))
9395
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))
9496
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxInternal, WebPDemuxer* (*)(const WebPData*, int, WebPDemuxState*, int))
95-
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetFrame, int (*)(const WebPDemuxer* dmux, int frame_number, WebPIterator* iter))
97+
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetFrame, int (*)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter))
98+
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxNextFrame, int (*)(WebPIterator *iter))
99+
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxReleaseIterator, void (*)(WebPIterator *iter))
96100
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetI, uint32_t (*)(const WebPDemuxer* dmux, WebPFormatFeature feature))
97101
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxDelete, void (*)(WebPDemuxer* dmux))
98102
}
@@ -266,16 +270,16 @@ IMG_Animation *IMG_LoadWEBPAnimation_IO(SDL_IOStream *src)
266270
{
267271
Sint64 start;
268272
const char *error = NULL;
269-
Uint32 format;
270273
WebPBitstreamFeatures features;
271-
struct WebPDemuxer* dmuxer = NULL;
274+
struct WebPDemuxer *demuxer = NULL;
272275
WebPIterator iter;
273276
IMG_Animation *anim = NULL;
274277
size_t raw_data_size;
275278
uint8_t *raw_data = NULL;
276-
uint8_t *ret;
277-
int frame_idx;
278279
WebPData wd;
280+
uint32_t bgcolor;
281+
SDL_Surface *canvas = NULL;
282+
WebPMuxAnimDispose dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
279283

280284
if (!src) {
281285
/* The error message has been set in SDL_IOFromFile */
@@ -296,79 +300,119 @@ IMG_Animation *IMG_LoadWEBPAnimation_IO(SDL_IOStream *src)
296300

297301
raw_data = (uint8_t*) SDL_malloc(raw_data_size);
298302
if (raw_data == NULL) {
299-
error = "Failed to allocate enough buffer for WEBP Animation";
300303
goto error;
301304
}
302305

303306
if (SDL_ReadIO(src, raw_data, raw_data_size) != raw_data_size) {
304-
error = "Failed to read WEBP Animation";
305307
goto error;
306308
}
307309

308310
if (lib.WebPGetFeaturesInternal(raw_data, raw_data_size, &features, WEBP_DECODER_ABI_VERSION) != VP8_STATUS_OK) {
309-
error = "WebPGetFeatures has failed";
311+
error = "WebPGetFeatures() failed";
310312
goto error;
311313
}
312314

313-
if (features.has_alpha) {
314-
format = SDL_PIXELFORMAT_RGBA32;
315-
} else {
316-
format = SDL_PIXELFORMAT_RGB24;
317-
}
318-
319315
wd.size = raw_data_size;
320316
wd.bytes = raw_data;
321-
dmuxer = lib.WebPDemuxInternal(&wd, 0, NULL, WEBP_DEMUX_ABI_VERSION);
322-
anim = (IMG_Animation *)SDL_malloc(sizeof(IMG_Animation));
317+
demuxer = lib.WebPDemuxInternal(&wd, 0, NULL, WEBP_DEMUX_ABI_VERSION);
318+
if (!demuxer) {
319+
error = "WebPDemux() failed";
320+
goto error;
321+
}
322+
323+
anim = (IMG_Animation *)SDL_calloc(1, sizeof(*anim));
324+
if (!anim) {
325+
goto error;
326+
}
323327
anim->w = features.width;
324328
anim->h = features.height;
325-
anim->count = lib.WebPDemuxGetI(dmuxer, WEBP_FF_FRAME_COUNT);
329+
anim->count = lib.WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
326330
anim->frames = (SDL_Surface **)SDL_calloc(anim->count, sizeof(*anim->frames));
327331
anim->delays = (int *)SDL_calloc(anim->count, sizeof(*anim->delays));
328-
for (frame_idx = 0; frame_idx < (anim->count); frame_idx++) {
329-
SDL_Surface* curr;
330-
if (lib.WebPDemuxGetFrame(dmuxer, frame_idx, &iter) == 0) {
331-
break;
332-
}
333-
curr = SDL_CreateSurface(features.width, features.height, format);
334-
if (curr == NULL) {
335-
error = "Failed to allocate SDL_Surface";
336-
goto error;
337-
}
338-
anim->frames[frame_idx] = curr;
339-
anim->delays[frame_idx] = iter.duration;
340-
if (features.has_alpha) {
341-
ret = lib.WebPDecodeRGBAInto(
342-
iter.fragment.bytes,
343-
iter.fragment.size,
344-
(uint8_t *)curr->pixels,
345-
curr->pitch * curr->h,
346-
curr->pitch);
347-
} else {
348-
ret = lib.WebPDecodeRGBInto(
349-
iter.fragment.bytes, iter.fragment.size,
350-
(uint8_t *)curr->pixels,
351-
curr->pitch * curr->h,
352-
curr->pitch);
353-
}
354-
if (ret == NULL) {
355-
break;
356-
}
357-
}
358-
if (dmuxer) {
359-
lib.WebPDemuxDelete(dmuxer);
332+
333+
canvas = SDL_CreateSurface(anim->w, anim->h, features.has_alpha ? SDL_PIXELFORMAT_RGBA32 : SDL_PIXELFORMAT_RGBX32);
334+
if (!canvas) {
335+
goto error;
360336
}
361337

362-
if (raw_data) {
363-
SDL_free(raw_data);
338+
/* Background color is BGRA byte order according to the spec */
339+
bgcolor = lib.WebPDemuxGetI(demuxer, WEBP_FF_BACKGROUND_COLOR);
340+
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
341+
bgcolor = SDL_MapSurfaceRGBA(canvas,
342+
(bgcolor >> 8) & 0xFF,
343+
(bgcolor >> 16) & 0xFF,
344+
(bgcolor >> 24) & 0xFF,
345+
(bgcolor >> 0) & 0xFF);
346+
#else
347+
bgcolor = SDL_MapSurfaceRGBA(canvas,
348+
(bgcolor >> 16) & 0xFF,
349+
(bgcolor >> 8) & 0xFF,
350+
(bgcolor >> 0) & 0xFF,
351+
(bgcolor >> 24) & 0xFF);
352+
#endif
353+
354+
SDL_zero(iter);
355+
if (lib.WebPDemuxGetFrame(demuxer, 1, &iter)) {
356+
do {
357+
int frame_idx = (iter.frame_num - 1);
358+
if (frame_idx < 0 || frame_idx >= anim->count) {
359+
continue;
360+
}
361+
362+
if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
363+
SDL_FillSurfaceRect(canvas, NULL, bgcolor);
364+
}
365+
366+
SDL_Surface *curr = SDL_CreateSurface(iter.width, iter.height, SDL_PIXELFORMAT_RGBA32);
367+
if (!curr) {
368+
goto error;
369+
}
370+
371+
if (!lib.WebPDecodeRGBAInto(iter.fragment.bytes,
372+
iter.fragment.size,
373+
(uint8_t *)curr->pixels,
374+
curr->pitch * curr->h,
375+
curr->pitch)) {
376+
error = "WebPDecodeRGBAInto() failed";
377+
SDL_DestroySurface(curr);
378+
goto error;
379+
}
380+
381+
SDL_Rect dst = { iter.x_offset, iter.y_offset, iter.width, iter.height };
382+
if (iter.blend_method == WEBP_MUX_BLEND) {
383+
SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_BLEND);
384+
} else {
385+
SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_NONE);
386+
}
387+
SDL_BlitSurface(curr, NULL, canvas, &dst);
388+
SDL_DestroySurface(curr);
389+
390+
anim->frames[frame_idx] = SDL_DuplicateSurface(canvas);
391+
anim->delays[frame_idx] = iter.duration;
392+
dispose_method = iter.dispose_method;
393+
394+
} while (lib.WebPDemuxNextFrame(&iter));
395+
396+
lib.WebPDemuxReleaseIterator(&iter);
364397
}
398+
399+
SDL_DestroySurface(canvas);
400+
401+
lib.WebPDemuxDelete(demuxer);
402+
403+
SDL_free(raw_data);
404+
365405
return anim;
406+
366407
error:
408+
if (canvas) {
409+
SDL_DestroySurface(canvas);
410+
}
367411
if (anim) {
368412
IMG_FreeAnimation(anim);
369413
}
370-
if (dmuxer) {
371-
lib.WebPDemuxDelete(dmuxer);
414+
if (demuxer) {
415+
lib.WebPDemuxDelete(demuxer);
372416
}
373417
if (raw_data) {
374418
SDL_free(raw_data);

0 commit comments

Comments
 (0)