diff options
author | Michael Drake <michael.drake@codethink.co.uk> | 2020-07-06 16:09:36 +0100 |
---|---|---|
committer | Michael Drake <michael.drake@codethink.co.uk> | 2020-07-06 16:09:36 +0100 |
commit | 8442a27c2bb8df48029ceea6e64c4930106a57fc (patch) | |
tree | 1c8829cbd31e254550cf4661f7adcbb16c9113b5 | |
parent | a937d161f6787ecc1c1bef9bad6039925ff13d72 (diff) | |
download | libnsgif-8442a27c2bb8df48029ceea6e64c4930106a57fc.tar.gz libnsgif-8442a27c2bb8df48029ceea6e64c4930106a57fc.tar.bz2 |
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
-rw-r--r-- | include/libnsgif.h | 9 | ||||
-rw-r--r-- | src/libnsgif.c | 101 |
2 files changed, 89 insertions, 21 deletions
diff --git a/include/libnsgif.h b/include/libnsgif.h index a819fec..50dc688 100644 --- a/include/libnsgif.h +++ b/include/libnsgif.h @@ -136,6 +136,15 @@ typedef struct gif_animation { unsigned int *global_colour_table; /** local colour table */ unsigned int *local_colour_table; + + /** previous frame for GIF_FRAME_RESTORE */ + void *prev_frame; + /** previous frame index */ + int prev_index; + /** previous frame width */ + unsigned prev_width; + /** previous frame height */ + unsigned prev_height; } gif_animation; /** 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; } |