From 8442a27c2bb8df48029ceea6e64c4930106a57fc Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Mon, 6 Jul 2020 16:09:36 +0100 Subject: Disposal Method: Handle Restore to previous with saved image. Previously we decoded a previous frame over the current frame data to handle resoration. However, the previous frame depended on its own previous frame state for correct decode. Now we just make a copy of the previous frame data and copy it back to handle the GIF_FRAME_RESTORE case. See: https://github.com/libvips/libvips/issues/1084#issuecomment-653497200 --- src/libnsgif.c | 101 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/libnsgif.c b/src/libnsgif.c index 6bf9956..95bbbfb 100644 --- a/src/libnsgif.c +++ b/src/libnsgif.c @@ -570,6 +570,73 @@ static gif_result gif_error_from_lzw(lzw_result l_res) return g_res[l_res]; } +static void gif__record_previous_frame(gif_animation *gif) +{ + bool need_alloc = gif->prev_frame == NULL; + const uint32_t *frame_data; + uint32_t *prev_frame; + + if (gif->decoded_frame == GIF_INVALID_FRAME || + gif->decoded_frame == gif->prev_index) { + /* No frame to copy, or already have this frame recorded. */ + return; + } + + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return; + } + + if (gif->prev_frame != NULL && + gif->width * gif->height < gif->prev_width * gif->prev_height) { + need_alloc = true; + } + + if (need_alloc) { + prev_frame = realloc(gif->prev_frame, + gif->width * gif->height * 4); + if (prev_frame == NULL) { + return; + } + } else { + prev_frame = gif->prev_frame; + } + + memcpy(prev_frame, frame_data, gif->width * gif->height * 4); + + gif->prev_frame = prev_frame; + gif->prev_width = gif->width; + gif->prev_height = gif->height; + gif->prev_index = gif->decoded_frame; +} + +static gif_result gif__recover_previous_frame(const gif_animation *gif) +{ + const uint32_t *prev_frame = gif->prev_frame; + unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; + unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; + uint32_t *frame_data; + + if (prev_frame == NULL) { + return GIF_FRAME_DATA_ERROR; + } + + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return GIF_INSUFFICIENT_MEMORY; + } + + for (unsigned y = 0; y < height; y++) { + memcpy(frame_data, prev_frame, width * 4); + + frame_data += gif->width; + prev_frame += gif->prev_width; + } + + return GIF_OK; +} /** * decode a gif frame @@ -583,6 +650,7 @@ gif_internal_decode_frame(gif_animation *gif, unsigned int frame, bool clear_image) { + gif_result err; unsigned int index = 0; unsigned char *gif_data, *gif_end; int gif_bytes; @@ -612,6 +680,11 @@ gif_internal_decode_frame(gif_animation *gif, return GIF_OK; } + if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) { + /* Store the previous frame for later restoration */ + gif__record_previous_frame(gif); + } + /* Get the start of our frame data and the end of the GIF data */ gif_data = gif->gif_data + gif->frames[frame].frame_pointer; gif_end = gif->gif_data + gif->buffer_size; @@ -788,34 +861,16 @@ gif_internal_decode_frame(gif_animation *gif, (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) { /* * If the previous frame's disposal method requires we - * restore the previous image, find the last image set - * to "do not dispose" and get that frame data + * restore the previous image, restore our saved image. */ - int last_undisposed_frame = frame - 2; - while ((last_undisposed_frame >= 0) && - (gif->frames[last_undisposed_frame].disposal_method == GIF_FRAME_RESTORE)) { - last_undisposed_frame--; - } - - /* If we don't find one, clear the frame data */ - if (last_undisposed_frame == -1) { + err = gif__recover_previous_frame(gif); + if (err != GIF_OK) { /* see notes above on transparency * vs. background color */ memset((char*)frame_data, GIF_TRANSPARENT_COLOUR, gif->width * gif->height * sizeof(int)); - } else { - return_value = gif_internal_decode_frame(gif, last_undisposed_frame, false); - if (return_value != GIF_OK) { - goto gif_decode_frame_exit; - } - /* Get this frame's data */ - assert(gif->bitmap_callbacks.bitmap_get_buffer); - frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); - if (!frame_data) { - return GIF_INSUFFICIENT_MEMORY; - } } } gif->decoded_frame = frame; @@ -923,6 +978,7 @@ void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks) memset(gif, 0, sizeof(gif_animation)); gif->bitmap_callbacks = *bitmap_callbacks; gif->decoded_frame = GIF_INVALID_FRAME; + gif->prev_index = GIF_INVALID_FRAME; } @@ -1164,6 +1220,9 @@ void gif_finalise(gif_animation *gif) free(gif->global_colour_table); gif->global_colour_table = NULL; + free(gif->prev_frame); + gif->prev_frame = NULL; + lzw_context_destroy(gif->lzw_ctx); gif->lzw_ctx = NULL; } -- cgit v1.2.3