Gitweb links:
...log
http://git.netsurf-browser.org/libnsgif.git/shortlog/f2c94c53e1a05e11bd5e...
...commit
http://git.netsurf-browser.org/libnsgif.git/commit/f2c94c53e1a05e11bd5efd...
...tree
http://git.netsurf-browser.org/libnsgif.git/tree/f2c94c53e1a05e11bd5efda7...
The branch, tlsa/rewrite has been updated
via f2c94c53e1a05e11bd5efda79748f847f24c7f7d (commit)
via f19df341f8a3f3ec5a319fd4621b4a8f53699dcb (commit)
via ec55ce31acd8aa8b2601db2a74e5b1725fa61da2 (commit)
via 170edd84170a8de5801dcf5c022a562374f83b2f (commit)
via 1e4a781b695992fbe09459baa3518637fba96223 (commit)
via c17ab37852fadbc6fe7ae80a969aa408f3d809b1 (commit)
via 0f7bcedeedff2cdee82644213741b5b96d655d92 (commit)
via 22b5b8f627a770172fbcc9e37656dcf40836f5df (commit)
via 1587b6a1a936ba11dfc951d07766562dbb4e8cf9 (commit)
from d96f028f305b5286618dcfc682e0de2515b3b7bd (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=f2c94c53e1a05e11bd...
commit f2c94c53e1a05e11bd5efda79748f847f24c7f7d
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Remove redundant check and comment from frame initialiser.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 366b4ef..1280798 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -991,14 +991,6 @@ static gif_result gif_initialise_frame(
return GIF_OK;
}
- /* Check if there is enough data remaining. The shortest block of data
- * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif
- * trailer
- */
- if (gif_bytes < 6) {
- return GIF_INSUFFICIENT_DATA;
- }
-
/* We could theoretically get some junk data that gives us millions of
* frames, so we ensure that we don't have a silly number
*/
@@ -1011,12 +1003,6 @@ static gif_result gif_initialise_frame(
return GIF_INSUFFICIENT_MEMORY;
}
- /* We pretend to initialise the frames, but really we just skip over
- * all the data contained within. This is all basically a cut down
- * version of gif_decode_frame that doesn't have any of the LZW bits in
- * it.
- */
-
/* Initialise any extensions */
gif->buffer_position = gif_data - gif->gif_data;
ret = gif__parse_frame_extensions(gif, &gif->frames[frame_idx], true);
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=f19df341f8a3f3ec5a...
commit f19df341f8a3f3ec5a319fd4621b4a8f53699dcb
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Use image parsing frunction for frame initialisation.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index fc826a8..366b4ef 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -980,7 +980,6 @@ static gif_result gif_initialise_frame(
struct gif_frame *frame;
uint8_t *gif_data, *gif_end;
int gif_bytes;
- uint32_t block_size;
/* Get our buffer position etc. */
gif_data = (uint8_t *)(gif->gif_data + gif->buffer_position);
@@ -1034,67 +1033,13 @@ static gif_result gif_initialise_frame(
if (ret != GIF_OK) {
return ret;
}
- gif_data = gif->gif_data + gif->buffer_position;
- gif_bytes = (gif_end - gif_data);
- /* Move our data onwards and remember we've got a bit of this frame */
- gif->frame_count_partial = frame_idx + 1;
-
- /* Ensure we have a correct code size */
- if (gif_bytes < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- if (gif_data[0] >= LZW_CODE_MAX) {
- return GIF_DATA_ERROR;
- }
-
- /* Move our pointer to the actual image data */
- gif_data++;
- --gif_bytes;
-
- /* Repeatedly skip blocks until we get a zero block or run out of data
- * These blocks of image data are processed later by gif_decode_frame()
- */
- block_size = 0;
- while (block_size != 1) {
- if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA;
- block_size = gif_data[0] + 1;
- /* Check if the frame data runs off the end of the file */
- if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
- */
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
- } else {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- } else {
- gif_bytes -= block_size;
- gif_data += block_size;
- }
+ ret = gif__parse_image_data(gif, &gif->frames[frame_idx], false);
+ if (ret != GIF_OK) {
+ return ret;
}
- /* Add the frame and set the display flag */
- gif->buffer_position = gif_data - gif->gif_data;
- gif->frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
-
- /* Check if we've finished */
- if (gif_bytes < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- } else {
- if (gif_data[0] == GIF_TRAILER) {
- return GIF_OK;
- }
- }
- return GIF_WORKING;
+ return GIF_OK;
}
/**
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=ec55ce31acd8aa8b26...
commit ec55ce31acd8aa8b2601db2a74e5b1725fa61da2
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Reorder functions so frame initialise can use image decoder.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 3b1df7e..fc826a8 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -70,6 +70,21 @@ enum gif_disposal {
/** standard GIF header size */
#define GIF_STANDARD_HEADER_SIZE 13
+static gif_result gif_error_from_lzw(lzw_result l_res)
+{
+ static const gif_result g_res[] = {
+ [LZW_OK] = GIF_OK,
+ [LZW_OK_EOD] = GIF_END_OF_FRAME,
+ [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY,
+ [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA,
+ [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR,
+ [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR,
+ [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR,
+ };
+ assert(l_res != LZW_BAD_PARAM);
+ assert(l_res != LZW_NO_COLOUR);
+ return g_res[l_res];
+}
/**
* Updates the sprite memory size
@@ -79,10 +94,10 @@ enum gif_disposal {
* \param height The height of the sprite
* \return GIF_INSUFFICIENT_MEMORY for a memory error GIF_OK for success
*/
-static gif_result
-gif_initialise_sprite(gif_animation *gif,
- uint32_t width,
- uint32_t height)
+static gif_result gif_initialise_sprite(
+ struct gif_animation *gif,
+ uint32_t width,
+ uint32_t height)
{
/* Already allocated? */
if (gif->frame_image) {
@@ -99,909 +114,717 @@ gif_initialise_sprite(gif_animation *gif,
}
/**
- * Parse the application extension
+ * Helper to get the rendering bitmap for a gif.
*
- * \param[in] frame The gif object we're decoding.
- * \param[in] data The data to decode.
- * \param[in] len Byte length of data.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \param[in] gif The gif object we're decoding.
+ * \return Client pixel buffer for rendering into.
*/
-static gif_result gif__parse_extension_graphic_control(
- struct gif_frame *frame,
- uint8_t *data,
- size_t len)
+static inline uint32_t* gif__bitmap_get(
+ struct gif_animation *gif)
{
- /* 6-byte Graphic Control Extension is:
- *
- * +0 CHAR Graphic Control Label
- * +1 CHAR Block Size
- * +2 CHAR __Packed Fields__
- * 3BITS Reserved
- * 3BITS Disposal Method
- * 1BIT User Input Flag
- * 1BIT Transparent Color Flag
- * +3 SHORT Delay Time
- * +5 CHAR Transparent Color Index
- */
- if (len < 6) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- frame->frame_delay = data[3] | (data[4] << 8);
- if (data[2] & GIF_TRANSPARENCY_MASK) {
- frame->transparency = true;
- frame->transparency_index = data[5];
- }
+ gif_result ret;
- frame->disposal_method = ((data[2] & GIF_DISPOSAL_MASK) >> 2);
- /* I have encountered documentation and GIFs in the
- * wild that use 0x04 to restore the previous frame,
- * rather than the officially documented 0x03. I
- * believe some (older?) software may even actually
- * export this way. We handle this as a type of
- * "quirks" mode. */
- if (frame->disposal_method == GIF_DISPOSAL_RESTORE_QUIRK) {
- frame->disposal_method = GIF_DISPOSAL_RESTORE_PREV;
+ /* Make sure we have a buffer to decode to. */
+ ret = gif_initialise_sprite(gif, gif->width, gif->height);
+ if (ret != GIF_OK) {
+ return NULL;
}
- /* if we are clearing the background then we need to
- * redraw enough to cover the previous frame too. */
- frame->redraw_required =
- frame->disposal_method == GIF_DISPOSAL_RESTORE_BG ||
- frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV;
-
- return GIF_OK;
+ /* Get the frame data */
+ assert(gif->bitmap_callbacks.bitmap_get_buffer);
+ return (uint32_t *)gif->bitmap_callbacks.bitmap_get_buffer(
+ gif->frame_image);
}
/**
- * Parse the application extension
+ * Helper to tell the client that their bitmap was modified.
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] data The data to decode.
- * \param[in] len Byte length of data.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \param[in] gif The gif object we're decoding.
*/
-static gif_result gif__parse_extension_application(
- struct gif_animation *gif,
- uint8_t *data,
- size_t len)
+static inline void gif__bitmap_modified(
+ const struct gif_animation *gif)
{
- /* 14-byte+ Application Extension is:
- *
- * +0 CHAR Application Extension Label
- * +1 CHAR Block Size
- * +2 8CHARS Application Identifier
- * +10 3CHARS Appl. Authentication Code
- * +13 1-256 Application Data (Data sub-blocks)
- */
- if (len < 17) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ if (gif->bitmap_callbacks.bitmap_modified) {
+ gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
}
+}
- if ((data[1] == 0x0b) &&
- (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) &&
- (data[13] == 0x03) && (data[14] == 0x01)) {
- gif->loop_count = data[15] | (data[16] << 8);
+/**
+ * Helper to tell the client that whether the bitmap is opaque.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frmae The frame that has been decoded.
+ */
+static inline void gif__bitmap_set_opaque(
+ const struct gif_animation *gif,
+ const struct gif_frame *frame)
+{
+ if (gif->bitmap_callbacks.bitmap_set_opaque) {
+ gif->bitmap_callbacks.bitmap_set_opaque(
+ gif->frame_image, frame->opaque);
}
-
- return GIF_OK;
}
/**
- * Parse the frame's extensions
+ * Helper to get the client to determine if the bitmap is opaque.
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to parse extensions for.
- * \param[in] decode Whether to decode or skip over the extension.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \todo: We don't really need to get the client to do this for us.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \return true if the bitmap is opaque, false otherwise.
*/
-static gif_result gif__parse_frame_extensions(
- struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+static inline bool gif__bitmap_get_opaque(
+ const struct gif_animation *gif)
{
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
-
- /* Get our buffer position etc. */
- gif_data = gif->gif_data + gif->buffer_position;
- gif_end = gif->gif_data + gif->buffer_size;
- gif_bytes = gif_end - gif_data;
-
- /* Initialise the extensions */
- while (gif_bytes > 0 && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
- bool block_step = true;
- gif_result ret;
-
- gif_data++;
- gif_bytes--;
-
- if (gif_bytes == 0) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
+ if (gif->bitmap_callbacks.bitmap_test_opaque) {
+ return gif->bitmap_callbacks.bitmap_test_opaque(
+ gif->frame_image);
+ }
- /* Switch on extension label */
- switch (gif_data[0]) {
- case GIF_EXTENSION_GRAPHIC_CONTROL:
- if (decode) {
- ret = gif__parse_extension_graphic_control(
- frame, gif_data, gif_bytes);
- if (ret != GIF_OK) {
- return ret;
- }
- }
- break;
+ return false;
+}
- case GIF_EXTENSION_APPLICATION:
- if (decode) {
- ret = gif__parse_extension_application(
- gif, gif_data, gif_bytes);
- if (ret != GIF_OK) {
- return ret;
- }
- }
- break;
+static void gif__record_frame(
+ struct gif_animation *gif,
+ const uint32_t *bitmap)
+{
+ bool need_alloc = gif->prev_frame == NULL;
+ uint32_t *prev_frame;
- case GIF_EXTENSION_COMMENT:
- /* Move the pointer to the first data sub-block Skip 1
- * byte for the extension label. */
- ++gif_data;
- block_step = false;
- break;
+ if (gif->decoded_frame == GIF_INVALID_FRAME ||
+ gif->decoded_frame == gif->prev_index) {
+ /* No frame to copy, or already have this frame recorded. */
+ return;
+ }
- default:
- break;
- }
+ bitmap = gif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return;
+ }
- if (block_step) {
- /* Move the pointer to the first data sub-block Skip 2
- * bytes for the extension label and size fields Skip
- * the extension size itself
- */
- if (gif_bytes < 2) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- gif_data += 2 + gif_data[1];
- }
+ if (gif->prev_frame != NULL &&
+ gif->width * gif->height > gif->prev_width * gif->prev_height) {
+ need_alloc = true;
+ }
- /* Repeatedly skip blocks until we get a zero block or run out
- * of data. This data is ignored by this gif decoder. */
- while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
- gif_data += gif_data[0] + 1;
- if (gif_data >= gif_end) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
+ if (need_alloc) {
+ prev_frame = realloc(gif->prev_frame,
+ gif->width * gif->height * 4);
+ if (prev_frame == NULL) {
+ return;
}
- gif_data++;
- gif_bytes = gif_end - gif_data;
+ } else {
+ prev_frame = gif->prev_frame;
}
- if (gif_data > gif_end) {
- gif_data = gif_end;
- }
+ memcpy(prev_frame, bitmap, gif->width * gif->height * 4);
- /* Set buffer position and return */
- gif->buffer_position = gif_data - gif->gif_data;
- return GIF_OK;
+ gif->prev_frame = prev_frame;
+ gif->prev_width = gif->width;
+ gif->prev_height = gif->height;
+ gif->prev_index = gif->decoded_frame;
}
-/**
- * Parse a GIF Image Descriptor.
- *
- * The format is:
- *
- * +0 CHAR Image Separator (0x2c)
- * +1 SHORT Image Left Position
- * +3 SHORT Image Top Position
- * +5 SHORT Width
- * +7 SHORT Height
- * +9 CHAR __Packed Fields__
- * 1BIT Local Colour Table Flag
- * 1BIT Interlace Flag
- * 1BIT Sort Flag
- * 2BITS Reserved
- * 3BITS Size of Local Colour Table
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to parse an image descriptor for.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_image_descriptor(
- struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+static gif_result gif__recover_frame(
+ const struct gif_animation *gif,
+ uint32_t *bitmap)
{
- const uint8_t *data = gif->gif_data + gif->buffer_position;
- size_t len = gif->buffer_size - gif->buffer_position;
- enum {
- GIF_IMAGE_DESCRIPTOR_LEN = 10u,
- GIF_IMAGE_SEPARATOR = 0x2Cu,
- };
-
- assert(gif != NULL);
- assert(frame != NULL);
+ 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;
- if (len < GIF_IMAGE_DESCRIPTOR_LEN) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ if (prev_frame == NULL) {
+ return GIF_FRAME_DATA_ERROR;
}
- if (decode) {
- unsigned x, y, w, h;
-
- if (data[0] != GIF_IMAGE_SEPARATOR) {
- return GIF_FRAME_DATA_ERROR;
- }
-
- x = data[1] | (data[2] << 8);
- y = data[3] | (data[4] << 8);
- w = data[5] | (data[6] << 8);
- h = data[7] | (data[8] << 8);
- frame->flags = data[9];
-
- frame->redraw_x = x;
- frame->redraw_y = y;
- frame->redraw_width = w;
- frame->redraw_height = h;
+ for (unsigned y = 0; y < height; y++) {
+ memcpy(bitmap, prev_frame, width * 4);
- /* Frame size may have grown. */
- gif->width = (x + w > gif->width ) ? x + w : gif->width;
- gif->height = (y + h > gif->height) ? y + h : gif->height;
+ bitmap += gif->width;
+ prev_frame += gif->prev_width;
}
- gif->buffer_position += GIF_IMAGE_DESCRIPTOR_LEN;
return GIF_OK;
}
-/**
- * Get a frame's colour table.
- *
- * Sets up gif->colour_table for the frame.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to get the colour table for.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_colour_table(
- struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+static uint32_t gif_interlaced_line(int height, int y)
{
- const uint8_t *data = gif->gif_data + gif->buffer_position;
- size_t len = gif->buffer_size - gif->buffer_position;
- unsigned colour_table_size;
-
- assert(gif != NULL);
- assert(frame != NULL);
-
- if ((frame->flags & GIF_COLOUR_TABLE_MASK) == 0) {
- gif->colour_table = gif->global_colour_table;
- return GIF_OK;
+ if ((y << 3) < height) {
+ return (y << 3);
}
-
- colour_table_size = 2 << (frame->flags & GIF_COLOUR_TABLE_SIZE_MASK);
- if (len < colour_table_size * 3) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ y -= ((height + 7) >> 3);
+ if ((y << 3) < (height - 4)) {
+ return (y << 3) + 4;
}
-
- if (decode) {
- int count = colour_table_size;
- uint8_t *entry = (uint8_t *)gif->local_colour_table;
-
- while (count--) {
- /* Gif colour map contents are r,g,b.
- *
- * We want to pack them bytewise into the
- * colour table, such that the red component
- * is in byte 0 and the alpha component is in
- * byte 3.
- */
-
- *entry++ = *data++; /* r */
- *entry++ = *data++; /* g */
- *entry++ = *data++; /* b */
- *entry++ = 0xff; /* a */
- }
+ y -= ((height + 3) >> 3);
+ if ((y << 2) < (height - 2)) {
+ return (y << 2) + 2;
}
-
- gif->buffer_position += colour_table_size * 3;
- gif->colour_table = gif->local_colour_table;
- return GIF_OK;
+ y -= ((height + 1) >> 2);
+ return (y << 1) + 1;
}
-static struct gif_frame *gif__get_frame(
+static gif_result gif__decode_complex(
struct gif_animation *gif,
- uint32_t frame_idx)
+ uint32_t width,
+ uint32_t height,
+ uint32_t offset_x,
+ uint32_t offset_y,
+ uint32_t interlace,
+ uint8_t minimum_code_size,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
{
- struct gif_frame *frame;
+ uint32_t available = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
- if (gif->frame_holders > frame_idx) {
- frame = &gif->frames[frame_idx];
- } else {
- /* Allocate more memory */
- size_t count = frame_idx + 1;
- struct gif_frame *temp;
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init(gif->lzw_ctx, minimum_code_size,
+ gif->gif_data, gif->buffer_size, gif->buffer_position);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
+ }
- temp = realloc(gif->frames, count * sizeof(*frame));
- if (temp == NULL) {
- return NULL;
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t x;
+ uint32_t decode_y;
+ uint32_t *frame_scanline;
+
+ if (interlace) {
+ decode_y = gif_interlaced_line(height, y) + offset_y;
+ } else {
+ decode_y = y + offset_y;
}
- gif->frames = temp;
- gif->frame_holders = count;
+ frame_scanline = frame_data + offset_x + (decode_y * gif->width);
- frame = &gif->frames[frame_idx];
+ x = width;
+ while (x > 0) {
+ const uint8_t *uncompressed;
+ unsigned row_available;
+ if (available == 0) {
+ if (res != LZW_OK) {
+ /* Unexpected end of frame, try to recover */
+ if (res == LZW_OK_EOD) {
+ ret = GIF_OK;
+ } else {
+ ret = gif_error_from_lzw(res);
+ }
+ break;
+ }
+ res = lzw_decode(gif->lzw_ctx,
+ &uncompressed, &available);
+ }
- frame->transparency = false;
- frame->transparency_index = GIF_NO_TRANSPARENCY;
- frame->frame_pointer = gif->buffer_position;
- frame->redraw_required = false;
- frame->disposal_method = 0;
- frame->frame_delay = 100;
- frame->display = false;
- frame->virgin = true;
+ row_available = x < available ? x : available;
+ x -= row_available;
+ available -= row_available;
+ if (transparency_index > 0xFF) {
+ while (row_available-- > 0) {
+ *frame_scanline++ =
+ colour_table[*uncompressed++];
+ }
+ } else {
+ while (row_available-- > 0) {
+ register uint32_t colour;
+ colour = *uncompressed++;
+ if (colour != transparency_index) {
+ *frame_scanline =
+ colour_table[colour];
+ }
+ frame_scanline++;
+ }
+ }
+ }
}
-
- return frame;
+ return ret;
}
-/**
- * Attempts to initialise the next frame
- *
- * \param[in] gif The animation context
- * \param[in] frame_idx The frame number to decode.
- * \return error code
- * - GIF_INSUFFICIENT_DATA for insufficient data to do anything
- * - GIF_FRAME_DATA_ERROR for GIF frame data error
- * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
- * - GIF_DATA_ERROR for GIF error (invalid frame header)
- * - GIF_OK for successful decoding
- * - GIF_WORKING for successful decoding if more frames are expected
-*/
-static gif_result gif_initialise_frame(gif_animation *gif,
- uint32_t frame_idx)
+static gif_result gif__decode_simple(
+ struct gif_animation *gif,
+ uint32_t height,
+ uint32_t offset_y,
+ uint8_t minimum_code_size,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
{
- gif_result ret;
- struct gif_frame *frame;
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
- uint32_t block_size;
-
- /* Get our buffer position etc. */
- gif_data = (uint8_t *)(gif->gif_data + gif->buffer_position);
- gif_end = (uint8_t *)(gif->gif_data + gif->buffer_size);
- gif_bytes = (gif_end - gif_data);
-
- /* Check if we've finished */
- if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) {
- return GIF_OK;
- }
-
- /* Check if there is enough data remaining. The shortest block of data
- * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif
- * trailer
- */
- if (gif_bytes < 6) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- /* We could theoretically get some junk data that gives us millions of
- * frames, so we ensure that we don't have a silly number
- */
- if (frame_idx > 4096) {
- return GIF_FRAME_DATA_ERROR;
- }
-
- frame = gif__get_frame(gif, frame_idx);
- if (frame == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* We pretend to initialise the frames, but really we just skip over
- * all the data contained within. This is all basically a cut down
- * version of gif_decode_frame that doesn't have any of the LZW bits in
- * it.
- */
-
- /* Initialise any extensions */
- gif->buffer_position = gif_data - gif->gif_data;
- ret = gif__parse_frame_extensions(gif, &gif->frames[frame_idx], true);
- if (ret != GIF_OK) {
- return ret;
- }
-
- ret = gif__parse_image_descriptor(gif, &gif->frames[frame_idx], true);
- if (ret != GIF_OK) {
- return ret;
- }
-
- ret = gif__parse_colour_table(gif, &gif->frames[frame_idx], false);
- if (ret != GIF_OK) {
- return ret;
- }
- gif_data = gif->gif_data + gif->buffer_position;
- gif_bytes = (gif_end - gif_data);
-
- /* Move our data onwards and remember we've got a bit of this frame */
- gif->frame_count_partial = frame_idx + 1;
+ uint32_t pixels = gif->width * height;
+ uint32_t written = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
- /* Ensure we have a correct code size */
- if (gif_bytes < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- if (gif_data[0] >= LZW_CODE_MAX) {
- return GIF_DATA_ERROR;
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init_map(gif->lzw_ctx,
+ minimum_code_size, transparency_index, colour_table,
+ gif->gif_data, gif->buffer_size, gif->buffer_position);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
}
- /* Move our pointer to the actual image data */
- gif_data++;
- --gif_bytes;
+ frame_data += (offset_y * gif->width);
- /* Repeatedly skip blocks until we get a zero block or run out of data
- * These blocks of image data are processed later by gif_decode_frame()
- */
- block_size = 0;
- while (block_size != 1) {
- if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA;
- block_size = gif_data[0] + 1;
- /* Check if the frame data runs off the end of the file */
- if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
- */
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
+ while (pixels > 0) {
+ res = lzw_decode_map(gif->lzw_ctx,
+ frame_data, pixels, &written);
+ pixels -= written;
+ frame_data += written;
+ if (res != LZW_OK) {
+ /* Unexpected end of frame, try to recover */
+ if (res == LZW_OK_EOD) {
+ ret = GIF_OK;
} else {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ ret = gif_error_from_lzw(res);
}
- } else {
- gif_bytes -= block_size;
- gif_data += block_size;
- }
- }
-
- /* Add the frame and set the display flag */
- gif->buffer_position = gif_data - gif->gif_data;
- gif->frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
-
- /* Check if we've finished */
- if (gif_bytes < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- } else {
- if (gif_data[0] == GIF_TRAILER) {
- return GIF_OK;
+ break;
}
}
- return GIF_WORKING;
-}
-static uint32_t gif_interlaced_line(int height, int y)
-{
- if ((y << 3) < height) {
- return (y << 3);
- }
- y -= ((height + 7) >> 3);
- if ((y << 3) < (height - 4)) {
- return (y << 3) + 4;
- }
- y -= ((height + 3) >> 3);
- if ((y << 2) < (height - 2)) {
- return (y << 2) + 2;
+ if (pixels == 0) {
+ ret = GIF_OK;
}
- y -= ((height + 1) >> 2);
- return (y << 1) + 1;
-}
-
-static gif_result gif_error_from_lzw(lzw_result l_res)
-{
- static const gif_result g_res[] = {
- [LZW_OK] = GIF_OK,
- [LZW_OK_EOD] = GIF_END_OF_FRAME,
- [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY,
- [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA,
- [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR,
- [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR,
- [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR,
- };
- assert(l_res != LZW_BAD_PARAM);
- assert(l_res != LZW_NO_COLOUR);
- return g_res[l_res];
+ return ret;
}
-/**
- * Helper to get the rendering bitmap for a gif.
- *
- * \param[in] gif The gif object we're decoding.
- * \return Client pixel buffer for rendering into.
- */
-static inline uint32_t* gif__bitmap_get(
- struct gif_animation *gif)
+static inline gif_result gif__decode(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint8_t minimum_code_size,
+ uint32_t *restrict frame_data)
{
gif_result ret;
+ uint32_t offset_x = frame->redraw_x;
+ uint32_t offset_y = frame->redraw_y;
+ uint32_t width = frame->redraw_width;
+ uint32_t height = frame->redraw_height;
+ uint32_t interlace = frame->flags & GIF_INTERLACE_MASK;
+ uint32_t transparency_index = frame->transparency_index;
+ uint32_t *restrict colour_table = gif->colour_table;
- /* Make sure we have a buffer to decode to. */
- ret = gif_initialise_sprite(gif, gif->width, gif->height);
- if (ret != GIF_OK) {
- return NULL;
- }
-
- /* Get the frame data */
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- return (uint32_t *)gif->bitmap_callbacks.bitmap_get_buffer(
- gif->frame_image);
-}
-
-/**
- * Helper to tell the client that their bitmap was modified.
- *
- * \param[in] gif The gif object we're decoding.
- */
-static inline void gif__bitmap_modified(
- const struct gif_animation *gif)
-{
- if (gif->bitmap_callbacks.bitmap_modified) {
- gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
+ if (interlace == false && width == gif->width && offset_x == 0) {
+ ret = gif__decode_simple(gif, height, offset_y,
+ minimum_code_size, transparency_index,
+ frame_data, colour_table);
+ } else {
+ ret = gif__decode_complex(gif, width, height,
+ offset_x, offset_y, interlace,
+ minimum_code_size, transparency_index,
+ frame_data, colour_table);
}
-}
-/**
- * Helper to tell the client that whether the bitmap is opaque.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] frmae The frame that has been decoded.
- */
-static inline void gif__bitmap_set_opaque(
- const struct gif_animation *gif,
- const struct gif_frame *frame)
-{
- if (gif->bitmap_callbacks.bitmap_set_opaque) {
- gif->bitmap_callbacks.bitmap_set_opaque(
- gif->frame_image, frame->opaque);
- }
+ return ret;
}
/**
- * Helper to get the client to determine if the bitmap is opaque.
- *
- * \todo: We don't really need to get the client to do this for us.
+ * Restore a GIF to the background colour.
*
- * \param[in] gif The gif object we're decoding.
- * \return true if the bitmap is opaque, false otherwise.
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to clear, or NULL.
+ * \param[in] bitmap The bitmap to clear the frame in.
*/
-static inline bool gif__bitmap_get_opaque(
- const struct gif_animation *gif)
+static void gif__restore_bg(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint32_t *bitmap)
{
- if (gif->bitmap_callbacks.bitmap_test_opaque) {
- return gif->bitmap_callbacks.bitmap_test_opaque(
- gif->frame_image);
- }
+ if (frame == NULL) {
+ memset(bitmap, GIF_TRANSPARENT_COLOUR,
+ gif->width * gif->height * sizeof(*bitmap));
+ } else {
+ uint32_t offset_x = frame->redraw_x;
+ uint32_t offset_y = frame->redraw_y;
+ uint32_t width = frame->redraw_width;
+ uint32_t height = frame->redraw_height;
- return false;
+ if (frame->display == false) {
+ return;
+ }
+
+ if (frame->transparency) {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ memset(scanline, GIF_TRANSPARENT_COLOUR,
+ width * sizeof(*bitmap));
+ }
+ } else {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ for (uint32_t x = 0; x < width; x++) {
+ scanline[x] = gif->bg_colour;
+ }
+ }
+ }
+ }
}
-static void gif__record_frame(
+static gif_result gif__update_bitmap(
struct gif_animation *gif,
- const uint32_t *bitmap)
+ struct gif_frame *frame,
+ uint8_t minimum_code_size,
+ uint32_t frame_idx)
{
- bool need_alloc = gif->prev_frame == NULL;
- uint32_t *prev_frame;
+ gif_result ret;
+ uint32_t *bitmap;
- if (gif->decoded_frame == GIF_INVALID_FRAME ||
- gif->decoded_frame == gif->prev_index) {
- /* No frame to copy, or already have this frame recorded. */
- return;
- }
+ gif->decoded_frame = frame_idx;
bitmap = gif__bitmap_get(gif);
if (bitmap == NULL) {
- return;
+ return GIF_INSUFFICIENT_MEMORY;
}
- if (gif->prev_frame != NULL &&
- gif->width * gif->height > gif->prev_width * gif->prev_height) {
- need_alloc = true;
- }
+ /* Handle any bitmap clearing/restoration required before decoding this
+ * frame. */
+ if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
+ gif__restore_bg(gif, NULL, bitmap);
- 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;
+ struct gif_frame *prev = &gif->frames[frame_idx - 1];
+
+ if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
+ gif__restore_bg(gif, prev, bitmap);
+
+ } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ ret = gif__recover_frame(gif, bitmap);
+ if (ret != GIF_OK) {
+ gif__restore_bg(gif, prev, bitmap);
+ }
+ }
}
- memcpy(prev_frame, bitmap, gif->width * gif->height * 4);
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ /* Store the previous frame for later restoration */
+ gif__record_frame(gif, bitmap);
+ }
- gif->prev_frame = prev_frame;
- gif->prev_width = gif->width;
- gif->prev_height = gif->height;
- gif->prev_index = gif->decoded_frame;
+ ret = gif__decode(gif, frame, minimum_code_size, bitmap);
+
+ gif__bitmap_modified(gif);
+
+ if (frame->virgin) {
+ frame->opaque = gif__bitmap_get_opaque(gif);
+ frame->virgin = false;
+ }
+ gif__bitmap_set_opaque(gif, frame);
+
+ return ret;
}
-static gif_result gif__recover_frame(
- const struct gif_animation *gif,
- uint32_t *bitmap)
+/**
+ * Parse the application extension
+ *
+ * \param[in] frame The gif object we're decoding.
+ * \param[in] data The data to decode.
+ * \param[in] len Byte length of data.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_extension_graphic_control(
+ struct gif_frame *frame,
+ uint8_t *data,
+ size_t len)
{
- 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;
+ /* 6-byte Graphic Control Extension is:
+ *
+ * +0 CHAR Graphic Control Label
+ * +1 CHAR Block Size
+ * +2 CHAR __Packed Fields__
+ * 3BITS Reserved
+ * 3BITS Disposal Method
+ * 1BIT User Input Flag
+ * 1BIT Transparent Color Flag
+ * +3 SHORT Delay Time
+ * +5 CHAR Transparent Color Index
+ */
+ if (len < 6) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
- if (prev_frame == NULL) {
- return GIF_FRAME_DATA_ERROR;
+ frame->frame_delay = data[3] | (data[4] << 8);
+ if (data[2] & GIF_TRANSPARENCY_MASK) {
+ frame->transparency = true;
+ frame->transparency_index = data[5];
}
- for (unsigned y = 0; y < height; y++) {
- memcpy(bitmap, prev_frame, width * 4);
+ frame->disposal_method = ((data[2] & GIF_DISPOSAL_MASK) >> 2);
+ /* I have encountered documentation and GIFs in the
+ * wild that use 0x04 to restore the previous frame,
+ * rather than the officially documented 0x03. I
+ * believe some (older?) software may even actually
+ * export this way. We handle this as a type of
+ * "quirks" mode. */
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->disposal_method = GIF_DISPOSAL_RESTORE_PREV;
+ }
- bitmap += gif->width;
- prev_frame += gif->prev_width;
+ /* if we are clearing the background then we need to
+ * redraw enough to cover the previous frame too. */
+ frame->redraw_required =
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_BG ||
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV;
+
+ return GIF_OK;
+}
+
+/**
+ * Parse the application extension
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] data The data to decode.
+ * \param[in] len Byte length of data.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_extension_application(
+ struct gif_animation *gif,
+ uint8_t *data,
+ size_t len)
+{
+ /* 14-byte+ Application Extension is:
+ *
+ * +0 CHAR Application Extension Label
+ * +1 CHAR Block Size
+ * +2 8CHARS Application Identifier
+ * +10 3CHARS Appl. Authentication Code
+ * +13 1-256 Application Data (Data sub-blocks)
+ */
+ if (len < 17) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ if ((data[1] == 0x0b) &&
+ (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) &&
+ (data[13] == 0x03) && (data[14] == 0x01)) {
+ gif->loop_count = data[15] | (data[16] << 8);
}
return GIF_OK;
}
-static gif_result
-gif__decode_complex(
+/**
+ * Parse the frame's extensions
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to parse extensions for.
+ * \param[in] decode Whether to decode or skip over the extension.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_frame_extensions(
struct gif_animation *gif,
- uint32_t width,
- uint32_t height,
- uint32_t offset_x,
- uint32_t offset_y,
- uint32_t interlace,
- uint8_t minimum_code_size,
- uint32_t transparency_index,
- uint32_t *restrict frame_data,
- uint32_t *restrict colour_table)
+ struct gif_frame *frame,
+ bool decode)
{
- uint32_t available = 0;
- gif_result ret = GIF_OK;
- lzw_result res;
+ uint8_t *gif_data, *gif_end;
+ int gif_bytes;
+
+ /* Get our buffer position etc. */
+ gif_data = gif->gif_data + gif->buffer_position;
+ gif_end = gif->gif_data + gif->buffer_size;
+ gif_bytes = gif_end - gif_data;
- /* Initialise the LZW decoding */
- res = lzw_decode_init(gif->lzw_ctx, minimum_code_size,
- gif->gif_data, gif->buffer_size, gif->buffer_position);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
- }
+ /* Initialise the extensions */
+ while (gif_bytes > 0 && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
+ bool block_step = true;
+ gif_result ret;
- for (uint32_t y = 0; y < height; y++) {
- uint32_t x;
- uint32_t decode_y;
- uint32_t *frame_scanline;
+ gif_data++;
+ gif_bytes--;
- if (interlace) {
- decode_y = gif_interlaced_line(height, y) + offset_y;
- } else {
- decode_y = y + offset_y;
+ if (gif_bytes == 0) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
}
- frame_scanline = frame_data + offset_x + (decode_y * gif->width);
- x = width;
- while (x > 0) {
- const uint8_t *uncompressed;
- unsigned row_available;
- if (available == 0) {
- if (res != LZW_OK) {
- /* Unexpected end of frame, try to recover */
- if (res == LZW_OK_EOD) {
- ret = GIF_OK;
- } else {
- ret = gif_error_from_lzw(res);
- }
- break;
+ /* Switch on extension label */
+ switch (gif_data[0]) {
+ case GIF_EXTENSION_GRAPHIC_CONTROL:
+ if (decode) {
+ ret = gif__parse_extension_graphic_control(
+ frame, gif_data, gif_bytes);
+ if (ret != GIF_OK) {
+ return ret;
}
- res = lzw_decode(gif->lzw_ctx,
- &uncompressed, &available);
}
+ break;
- row_available = x < available ? x : available;
- x -= row_available;
- available -= row_available;
- if (transparency_index > 0xFF) {
- while (row_available-- > 0) {
- *frame_scanline++ =
- colour_table[*uncompressed++];
- }
- } else {
- while (row_available-- > 0) {
- register uint32_t colour;
- colour = *uncompressed++;
- if (colour != transparency_index) {
- *frame_scanline =
- colour_table[colour];
- }
- frame_scanline++;
+ case GIF_EXTENSION_APPLICATION:
+ if (decode) {
+ ret = gif__parse_extension_application(
+ gif, gif_data, gif_bytes);
+ if (ret != GIF_OK) {
+ return ret;
}
}
- }
- }
- return ret;
-}
-
-static gif_result
-gif__decode_simple(
- struct gif_animation *gif,
- uint32_t height,
- uint32_t offset_y,
- uint8_t minimum_code_size,
- uint32_t transparency_index,
- uint32_t *restrict frame_data,
- uint32_t *restrict colour_table)
-{
- uint32_t pixels = gif->width * height;
- uint32_t written = 0;
- gif_result ret = GIF_OK;
- lzw_result res;
+ break;
- /* Initialise the LZW decoding */
- res = lzw_decode_init_map(gif->lzw_ctx,
- minimum_code_size, transparency_index, colour_table,
- gif->gif_data, gif->buffer_size, gif->buffer_position);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
- }
+ case GIF_EXTENSION_COMMENT:
+ /* Move the pointer to the first data sub-block Skip 1
+ * byte for the extension label. */
+ ++gif_data;
+ block_step = false;
+ break;
- frame_data += (offset_y * gif->width);
+ default:
+ break;
+ }
- while (pixels > 0) {
- res = lzw_decode_map(gif->lzw_ctx,
- frame_data, pixels, &written);
- pixels -= written;
- frame_data += written;
- if (res != LZW_OK) {
- /* Unexpected end of frame, try to recover */
- if (res == LZW_OK_EOD) {
- ret = GIF_OK;
- } else {
- ret = gif_error_from_lzw(res);
+ if (block_step) {
+ /* Move the pointer to the first data sub-block Skip 2
+ * bytes for the extension label and size fields Skip
+ * the extension size itself
+ */
+ if (gif_bytes < 2) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
}
- break;
+ gif_data += 2 + gif_data[1];
}
- }
- if (pixels == 0) {
- ret = GIF_OK;
+ /* Repeatedly skip blocks until we get a zero block or run out
+ * of data. This data is ignored by this gif decoder. */
+ while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
+ gif_data += gif_data[0] + 1;
+ if (gif_data >= gif_end) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ }
+ gif_data++;
+ gif_bytes = gif_end - gif_data;
}
- return ret;
-}
-
-static inline gif_result gif__decode(
- struct gif_animation *gif,
- struct gif_frame *frame,
- uint8_t minimum_code_size,
- uint32_t *restrict frame_data)
-{
- gif_result ret;
- uint32_t offset_x = frame->redraw_x;
- uint32_t offset_y = frame->redraw_y;
- uint32_t width = frame->redraw_width;
- uint32_t height = frame->redraw_height;
- uint32_t interlace = frame->flags & GIF_INTERLACE_MASK;
- uint32_t transparency_index = frame->transparency_index;
- uint32_t *restrict colour_table = gif->colour_table;
-
- if (interlace == false && width == gif->width && offset_x == 0) {
- ret = gif__decode_simple(gif, height, offset_y,
- minimum_code_size, transparency_index,
- frame_data, colour_table);
- } else {
- ret = gif__decode_complex(gif, width, height,
- offset_x, offset_y, interlace,
- minimum_code_size, transparency_index,
- frame_data, colour_table);
+ if (gif_data > gif_end) {
+ gif_data = gif_end;
}
- return ret;
+ /* Set buffer position and return */
+ gif->buffer_position = gif_data - gif->gif_data;
+ return GIF_OK;
}
/**
- * Restore a GIF to the background colour.
+ * Parse a GIF Image Descriptor.
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to clear, or NULL.
- * \param[in] bitmap The bitmap to clear the frame in.
+ * The format is:
+ *
+ * +0 CHAR Image Separator (0x2c)
+ * +1 SHORT Image Left Position
+ * +3 SHORT Image Top Position
+ * +5 SHORT Width
+ * +7 SHORT Height
+ * +9 CHAR __Packed Fields__
+ * 1BIT Local Colour Table Flag
+ * 1BIT Interlace Flag
+ * 1BIT Sort Flag
+ * 2BITS Reserved
+ * 3BITS Size of Local Colour Table
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to parse an image descriptor for.
+ * \return GIF_OK on success, appropriate error otherwise.
*/
-static void gif__restore_bg(
+static gif_result gif__parse_image_descriptor(
struct gif_animation *gif,
struct gif_frame *frame,
- uint32_t *bitmap)
+ bool decode)
{
- if (frame == NULL) {
- memset(bitmap, GIF_TRANSPARENT_COLOUR,
- gif->width * gif->height * sizeof(*bitmap));
- } else {
- uint32_t offset_x = frame->redraw_x;
- uint32_t offset_y = frame->redraw_y;
- uint32_t width = frame->redraw_width;
- uint32_t height = frame->redraw_height;
+ const uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ enum {
+ GIF_IMAGE_DESCRIPTOR_LEN = 10u,
+ GIF_IMAGE_SEPARATOR = 0x2Cu,
+ };
- if (frame->display == false) {
- return;
- }
+ assert(gif != NULL);
+ assert(frame != NULL);
- if (frame->transparency) {
- for (uint32_t y = 0; y < height; y++) {
- uint32_t *scanline = bitmap + offset_x +
- (offset_y + y) * gif->width;
- memset(scanline, GIF_TRANSPARENT_COLOUR,
- width * sizeof(*bitmap));
- }
- } else {
- for (uint32_t y = 0; y < height; y++) {
- uint32_t *scanline = bitmap + offset_x +
- (offset_y + y) * gif->width;
- for (uint32_t x = 0; x < width; x++) {
- scanline[x] = gif->bg_colour;
- }
- }
+ if (len < GIF_IMAGE_DESCRIPTOR_LEN) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ if (decode) {
+ unsigned x, y, w, h;
+
+ if (data[0] != GIF_IMAGE_SEPARATOR) {
+ return GIF_FRAME_DATA_ERROR;
}
+
+ x = data[1] | (data[2] << 8);
+ y = data[3] | (data[4] << 8);
+ w = data[5] | (data[6] << 8);
+ h = data[7] | (data[8] << 8);
+ frame->flags = data[9];
+
+ frame->redraw_x = x;
+ frame->redraw_y = y;
+ frame->redraw_width = w;
+ frame->redraw_height = h;
+
+ /* Frame size may have grown. */
+ gif->width = (x + w > gif->width ) ? x + w : gif->width;
+ gif->height = (y + h > gif->height) ? y + h : gif->height;
}
+
+ gif->buffer_position += GIF_IMAGE_DESCRIPTOR_LEN;
+ return GIF_OK;
}
-static gif_result gif__update_bitmap(
+/**
+ * Get a frame's colour table.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to get the colour table for.
+ * \return GIF_OK on success, appropriate error otherwise.
+ */
+static gif_result gif__parse_colour_table(
struct gif_animation *gif,
struct gif_frame *frame,
- uint8_t minimum_code_size,
- uint32_t frame_idx)
+ bool decode)
{
- gif_result ret;
- uint32_t *bitmap;
-
- gif->decoded_frame = frame_idx;
-
- bitmap = gif__bitmap_get(gif);
- if (bitmap == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* Handle any bitmap clearing/restoration required before decoding this
- * frame. */
- if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
- gif__restore_bg(gif, NULL, bitmap);
-
- } else {
- struct gif_frame *prev = &gif->frames[frame_idx - 1];
+ const uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ unsigned colour_table_size;
- if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
- gif__restore_bg(gif, prev, bitmap);
+ assert(gif != NULL);
+ assert(frame != NULL);
- } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
- ret = gif__recover_frame(gif, bitmap);
- if (ret != GIF_OK) {
- gif__restore_bg(gif, prev, bitmap);
- }
- }
+ if ((frame->flags & GIF_COLOUR_TABLE_MASK) == 0) {
+ gif->colour_table = gif->global_colour_table;
+ return GIF_OK;
}
- if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
- /* Store the previous frame for later restoration */
- gif__record_frame(gif, bitmap);
+ colour_table_size = 2 << (frame->flags & GIF_COLOUR_TABLE_SIZE_MASK);
+ if (len < colour_table_size * 3) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
}
- ret = gif__decode(gif, frame, minimum_code_size, bitmap);
+ if (decode) {
+ int count = colour_table_size;
+ uint8_t *entry = (uint8_t *)gif->local_colour_table;
- gif__bitmap_modified(gif);
+ while (count--) {
+ /* Gif colour map contents are r,g,b.
+ *
+ * We want to pack them bytewise into the
+ * colour table, such that the red component
+ * is in byte 0 and the alpha component is in
+ * byte 3.
+ */
- if (frame->virgin) {
- frame->opaque = gif__bitmap_get_opaque(gif);
- frame->virgin = false;
+ *entry++ = *data++; /* r */
+ *entry++ = *data++; /* g */
+ *entry++ = *data++; /* b */
+ *entry++ = 0xff; /* a */
+ }
}
- gif__bitmap_set_opaque(gif, frame);
- return ret;
+ gif->buffer_position += colour_table_size * 3;
+ gif->colour_table = gif->local_colour_table;
+ return GIF_OK;
}
/**
@@ -1100,6 +923,180 @@ static gif_result gif__parse_image_data(
return ret;
}
+static struct gif_frame *gif__get_frame(
+ struct gif_animation *gif,
+ uint32_t frame_idx)
+{
+ struct gif_frame *frame;
+
+ if (gif->frame_holders > frame_idx) {
+ frame = &gif->frames[frame_idx];
+ } else {
+ /* Allocate more memory */
+ size_t count = frame_idx + 1;
+ struct gif_frame *temp;
+
+ temp = realloc(gif->frames, count * sizeof(*frame));
+ if (temp == NULL) {
+ return NULL;
+ }
+ gif->frames = temp;
+ gif->frame_holders = count;
+
+ frame = &gif->frames[frame_idx];
+
+ frame->transparency = false;
+ frame->transparency_index = GIF_NO_TRANSPARENCY;
+ frame->frame_pointer = gif->buffer_position;
+ frame->redraw_required = false;
+ frame->disposal_method = 0;
+ frame->frame_delay = 100;
+ frame->display = false;
+ frame->virgin = true;
+ }
+
+ return frame;
+}
+
+/**
+ * Attempts to initialise the next frame
+ *
+ * \param[in] gif The animation context
+ * \param[in] frame_idx The frame number to decode.
+ * \return error code
+ * - GIF_INSUFFICIENT_DATA for insufficient data to do anything
+ * - GIF_FRAME_DATA_ERROR for GIF frame data error
+ * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
+ * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
+ * - GIF_DATA_ERROR for GIF error (invalid frame header)
+ * - GIF_OK for successful decoding
+ * - GIF_WORKING for successful decoding if more frames are expected
+*/
+static gif_result gif_initialise_frame(
+ struct gif_animation *gif,
+ uint32_t frame_idx)
+{
+ gif_result ret;
+ struct gif_frame *frame;
+ uint8_t *gif_data, *gif_end;
+ int gif_bytes;
+ uint32_t block_size;
+
+ /* Get our buffer position etc. */
+ gif_data = (uint8_t *)(gif->gif_data + gif->buffer_position);
+ gif_end = (uint8_t *)(gif->gif_data + gif->buffer_size);
+ gif_bytes = (gif_end - gif_data);
+
+ /* Check if we've finished */
+ if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) {
+ return GIF_OK;
+ }
+
+ /* Check if there is enough data remaining. The shortest block of data
+ * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif
+ * trailer
+ */
+ if (gif_bytes < 6) {
+ return GIF_INSUFFICIENT_DATA;
+ }
+
+ /* We could theoretically get some junk data that gives us millions of
+ * frames, so we ensure that we don't have a silly number
+ */
+ if (frame_idx > 4096) {
+ return GIF_FRAME_DATA_ERROR;
+ }
+
+ frame = gif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ /* We pretend to initialise the frames, but really we just skip over
+ * all the data contained within. This is all basically a cut down
+ * version of gif_decode_frame that doesn't have any of the LZW bits in
+ * it.
+ */
+
+ /* Initialise any extensions */
+ gif->buffer_position = gif_data - gif->gif_data;
+ ret = gif__parse_frame_extensions(gif, &gif->frames[frame_idx], true);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+
+ ret = gif__parse_image_descriptor(gif, &gif->frames[frame_idx], true);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+
+ ret = gif__parse_colour_table(gif, &gif->frames[frame_idx], false);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+ gif_data = gif->gif_data + gif->buffer_position;
+ gif_bytes = (gif_end - gif_data);
+
+ /* Move our data onwards and remember we've got a bit of this frame */
+ gif->frame_count_partial = frame_idx + 1;
+
+ /* Ensure we have a correct code size */
+ if (gif_bytes < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ if (gif_data[0] >= LZW_CODE_MAX) {
+ return GIF_DATA_ERROR;
+ }
+
+ /* Move our pointer to the actual image data */
+ gif_data++;
+ --gif_bytes;
+
+ /* Repeatedly skip blocks until we get a zero block or run out of data
+ * These blocks of image data are processed later by gif_decode_frame()
+ */
+ block_size = 0;
+ while (block_size != 1) {
+ if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA;
+ block_size = gif_data[0] + 1;
+ /* Check if the frame data runs off the end of the file */
+ if ((int)(gif_bytes - block_size) < 0) {
+ /* Try to recover by signaling the end of the gif.
+ * Once we get garbage data, there is no logical way to
+ * determine where the next frame is. It's probably
+ * better to partially load the gif than not at all.
+ */
+ if (gif_bytes >= 2) {
+ gif_data[0] = 0;
+ gif_data[1] = GIF_TRAILER;
+ gif_bytes = 1;
+ ++gif_data;
+ break;
+ } else {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ } else {
+ gif_bytes -= block_size;
+ gif_data += block_size;
+ }
+ }
+
+ /* Add the frame and set the display flag */
+ gif->buffer_position = gif_data - gif->gif_data;
+ gif->frame_count = frame_idx + 1;
+ gif->frames[frame_idx].display = true;
+
+ /* Check if we've finished */
+ if (gif_bytes < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ } else {
+ if (gif_data[0] == GIF_TRAILER) {
+ return GIF_OK;
+ }
+ }
+ return GIF_WORKING;
+}
+
/**
* decode a gif frame
*
@@ -1107,9 +1104,9 @@ static gif_result gif__parse_image_data(
* \param frame The frame number to decode.
* \param clear_image flag for image data being cleared instead of plotted.
*/
-static gif_result
-gif_internal_decode_frame(gif_animation *gif,
- uint32_t frame_idx)
+static gif_result gif_internal_decode_frame(
+ struct gif_animation *gif,
+ uint32_t frame_idx)
{
gif_result ret;
uint8_t *gif_data;
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=170edd84170a8de580...
commit 170edd84170a8de5801dcf5c022a562374f83b2f
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Split out image parsing.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index b527953..3b1df7e 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -960,6 +960,8 @@ static gif_result gif__update_bitmap(
gif_result ret;
uint32_t *bitmap;
+ gif->decoded_frame = frame_idx;
+
bitmap = gif__bitmap_get(gif);
if (bitmap == NULL) {
return GIF_INSUFFICIENT_MEMORY;
@@ -1003,6 +1005,102 @@ static gif_result gif__update_bitmap(
}
/**
+ * Parse the image data for a gif frame.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to get the colour table for.
+ * \return GIF_OK on success, appropriate error otherwise.
+ */
+static gif_result gif__parse_image_data(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ bool decode)
+{
+ uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ uint32_t frame_idx = frame - gif->frames;
+ uint8_t minimum_code_size;
+ gif_result ret;
+
+ assert(gif != NULL);
+ assert(frame != NULL);
+
+ if (!decode) {
+ gif->frame_count_partial = frame_idx + 1;
+ }
+
+ /* Ensure sufficient data remains. A gif trailer or a minimum lzw code
+ * followed by a gif trailer is treated as OK, although without any
+ * image data. */
+ switch (len) {
+ case 2: if (data[1] == GIF_TRAILER) return GIF_OK;
+ /* Fall through. */
+ case 1: if (data[0] == GIF_TRAILER) return GIF_OK;
+ /* Fall through. */
+ case 0: return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ minimum_code_size = data[0];
+ if (minimum_code_size >= LZW_CODE_MAX) {
+ return GIF_DATA_ERROR;
+ }
+ gif->buffer_position++;
+ data++;
+ len--;
+
+ if (decode) {
+ ret = gif__update_bitmap(gif, frame, minimum_code_size,
+ frame_idx);
+ } else {
+ uint32_t block_size = 0;
+
+ while (block_size != 1) {
+ if (len < 1) return GIF_INSUFFICIENT_FRAME_DATA;
+ block_size = data[0] + 1;
+ /* Check if the frame data runs off the end of the file */
+ if ((int)(len - block_size) < 0) {
+ /* Try to recover by signaling the end of the gif.
+ * Once we get garbage data, there is no logical way to
+ * determine where the next frame is. It's probably
+ * better to partially load the gif than not at all.
+ */
+ if (len >= 2) {
+ data[0] = 0;
+ data[1] = GIF_TRAILER;
+ len = 1;
+ data++;
+ break;
+ } else {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ } else {
+ len -= block_size;
+ data += block_size;
+ }
+ }
+
+ gif->buffer_position = data - gif->gif_data;
+ gif->frame_count = frame_idx + 1;
+ gif->frames[frame_idx].display = true;
+
+ /* Check if we've finished */
+ if (len < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ } else {
+ if (data[0] == GIF_TRAILER) {
+ return GIF_OK;
+ }
+ }
+
+ return GIF_WORKING;
+ }
+
+ return ret;
+}
+
+/**
* decode a gif frame
*
* \param gif gif animation context.
@@ -1014,9 +1112,8 @@ gif_internal_decode_frame(gif_animation *gif,
uint32_t frame_idx)
{
gif_result ret;
+ uint8_t *gif_data;
struct gif_frame *frame;
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
uint32_t save_buffer_position;
/* Ensure the frame is in range to decode */
@@ -1041,8 +1138,6 @@ gif_internal_decode_frame(gif_animation *gif,
/* Get the start of our frame data and the end of the GIF data */
gif_data = gif->gif_data + frame->frame_pointer;
- gif_end = gif->gif_data + gif->buffer_size;
- gif_bytes = (gif_end - gif_data);
/* Save the buffer position */
save_buffer_position = gif->buffer_position;
@@ -1061,44 +1156,14 @@ gif_internal_decode_frame(gif_animation *gif,
ret = gif__parse_colour_table(gif, frame, true);
if (ret != GIF_OK) {
- return ret;
- }
- gif_data = gif->gif_data + gif->buffer_position;
- gif_bytes = (gif_end - gif_data);
-
- /* Ensure sufficient data remains */
- if (gif_bytes < 1) {
- ret = GIF_INSUFFICIENT_FRAME_DATA;
- goto gif_decode_frame_exit;
- }
-
- /* check for an end marker */
- if (gif_data[0] == GIF_TRAILER) {
- ret = GIF_OK;
- goto gif_decode_frame_exit;
- }
-
- /* Ensure we have enough data for a 1-byte LZW code size +
- * 1-byte gif trailer
- */
- if (gif_bytes < 2) {
- ret = GIF_INSUFFICIENT_FRAME_DATA;
goto gif_decode_frame_exit;
}
- /* If we only have a 1-byte LZW code size + 1-byte gif trailer,
- * we're finished
- */
- if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) {
- ret = GIF_OK;
+ ret = gif__parse_image_data(gif, frame, true);
+ if (ret != GIF_OK) {
goto gif_decode_frame_exit;
}
- gif->decoded_frame = frame_idx;
- gif->buffer_position = (gif_data - gif->gif_data) + 1;
-
- ret = gif__update_bitmap(gif, frame, gif_data[0], frame_idx);
-
gif_decode_frame_exit:
/* Restore the buffer position */
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=1e4a781b695992fbe0...
commit 1e4a781b695992fbe09459baa3518637fba96223
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Bitmaps: Encapsulate bitmap handling.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 97d5b79..b527953 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -618,10 +618,82 @@ static gif_result gif_error_from_lzw(lzw_result l_res)
return g_res[l_res];
}
-static void gif__record_frame(gif_animation *gif)
+/**
+ * Helper to get the rendering bitmap for a gif.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \return Client pixel buffer for rendering into.
+ */
+static inline uint32_t* gif__bitmap_get(
+ struct gif_animation *gif)
+{
+ gif_result ret;
+
+ /* Make sure we have a buffer to decode to. */
+ ret = gif_initialise_sprite(gif, gif->width, gif->height);
+ if (ret != GIF_OK) {
+ return NULL;
+ }
+
+ /* Get the frame data */
+ assert(gif->bitmap_callbacks.bitmap_get_buffer);
+ return (uint32_t *)gif->bitmap_callbacks.bitmap_get_buffer(
+ gif->frame_image);
+}
+
+/**
+ * Helper to tell the client that their bitmap was modified.
+ *
+ * \param[in] gif The gif object we're decoding.
+ */
+static inline void gif__bitmap_modified(
+ const struct gif_animation *gif)
+{
+ if (gif->bitmap_callbacks.bitmap_modified) {
+ gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
+ }
+}
+
+/**
+ * Helper to tell the client that whether the bitmap is opaque.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frmae The frame that has been decoded.
+ */
+static inline void gif__bitmap_set_opaque(
+ const struct gif_animation *gif,
+ const struct gif_frame *frame)
+{
+ if (gif->bitmap_callbacks.bitmap_set_opaque) {
+ gif->bitmap_callbacks.bitmap_set_opaque(
+ gif->frame_image, frame->opaque);
+ }
+}
+
+/**
+ * Helper to get the client to determine if the bitmap is opaque.
+ *
+ * \todo: We don't really need to get the client to do this for us.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \return true if the bitmap is opaque, false otherwise.
+ */
+static inline bool gif__bitmap_get_opaque(
+ const struct gif_animation *gif)
+{
+ if (gif->bitmap_callbacks.bitmap_test_opaque) {
+ return gif->bitmap_callbacks.bitmap_test_opaque(
+ gif->frame_image);
+ }
+
+ return false;
+}
+
+static void gif__record_frame(
+ struct gif_animation *gif,
+ const uint32_t *bitmap)
{
bool need_alloc = gif->prev_frame == NULL;
- const uint32_t *frame_data;
uint32_t *prev_frame;
if (gif->decoded_frame == GIF_INVALID_FRAME ||
@@ -630,9 +702,8 @@ static void gif__record_frame(gif_animation *gif)
return;
}
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
+ bitmap = gif__bitmap_get(gif);
+ if (bitmap == NULL) {
return;
}
@@ -651,7 +722,7 @@ static void gif__record_frame(gif_animation *gif)
prev_frame = gif->prev_frame;
}
- memcpy(prev_frame, frame_data, gif->width * gif->height * 4);
+ memcpy(prev_frame, bitmap, gif->width * gif->height * 4);
gif->prev_frame = prev_frame;
gif->prev_width = gif->width;
@@ -659,27 +730,22 @@ static void gif__record_frame(gif_animation *gif)
gif->prev_index = gif->decoded_frame;
}
-static gif_result gif__recover_frame(const gif_animation *gif)
+static gif_result gif__recover_frame(
+ const struct gif_animation *gif,
+ uint32_t *bitmap)
{
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);
+ memcpy(bitmap, prev_frame, width * 4);
- frame_data += gif->width;
+ bitmap += gif->width;
prev_frame += gif->prev_width;
}
@@ -885,6 +951,57 @@ static void gif__restore_bg(
}
}
+static gif_result gif__update_bitmap(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint8_t minimum_code_size,
+ uint32_t frame_idx)
+{
+ gif_result ret;
+ uint32_t *bitmap;
+
+ bitmap = gif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ /* Handle any bitmap clearing/restoration required before decoding this
+ * frame. */
+ if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
+ gif__restore_bg(gif, NULL, bitmap);
+
+ } else {
+ struct gif_frame *prev = &gif->frames[frame_idx - 1];
+
+ if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
+ gif__restore_bg(gif, prev, bitmap);
+
+ } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ ret = gif__recover_frame(gif, bitmap);
+ if (ret != GIF_OK) {
+ gif__restore_bg(gif, prev, bitmap);
+ }
+ }
+ }
+
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ /* Store the previous frame for later restoration */
+ gif__record_frame(gif, bitmap);
+ }
+
+ ret = gif__decode(gif, frame, minimum_code_size, bitmap);
+
+ gif__bitmap_modified(gif);
+
+ if (frame->virgin) {
+ frame->opaque = gif__bitmap_get_opaque(gif);
+ frame->virgin = false;
+ }
+ gif__bitmap_set_opaque(gif, frame);
+
+ return ret;
+}
+
/**
* decode a gif frame
*
@@ -900,7 +1017,6 @@ gif_internal_decode_frame(gif_animation *gif,
struct gif_frame *frame;
uint8_t *gif_data, *gif_end;
int gif_bytes;
- uint32_t *frame_data = 0; // Set to 0 for no warnings
uint32_t save_buffer_position;
/* Ensure the frame is in range to decode */
@@ -962,20 +1078,6 @@ gif_internal_decode_frame(gif_animation *gif,
goto gif_decode_frame_exit;
}
- /* Make sure we have a buffer to decode to. */
- if (gif_initialise_sprite(gif, gif->width, gif->height)) {
- ret = GIF_INSUFFICIENT_MEMORY;
- goto gif_decode_frame_exit;
- }
-
- /* Get the frame data */
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
- ret = GIF_INSUFFICIENT_MEMORY;
- goto gif_decode_frame_exit;
- }
-
/* Ensure we have enough data for a 1-byte LZW code size +
* 1-byte gif trailer
*/
@@ -992,59 +1094,13 @@ gif_internal_decode_frame(gif_animation *gif,
goto gif_decode_frame_exit;
}
- /* Handle any bitmap clearing/restoration required before decoding this
- * frame. */
- if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
- gif__restore_bg(gif, NULL, frame_data);
-
- } else {
- struct gif_frame *prev = &gif->frames[frame_idx - 1];
-
- if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
- gif__restore_bg(gif, prev, frame_data);
-
- } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
- /*
- * If the previous frame's disposal method requires we
- * restore the previous image, restore our saved image.
- */
- ret = gif__recover_frame(gif);
- if (ret != GIF_OK) {
- gif__restore_bg(gif, prev, frame_data);
- }
- }
- }
-
- if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
- /* Store the previous frame for later restoration */
- gif__record_frame(gif);
- }
-
gif->decoded_frame = frame_idx;
gif->buffer_position = (gif_data - gif->gif_data) + 1;
- ret = gif__decode(gif, frame, gif_data[0], frame_data);
+ ret = gif__update_bitmap(gif, frame, gif_data[0], frame_idx);
gif_decode_frame_exit:
- if (gif->bitmap_callbacks.bitmap_modified) {
- gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
- }
-
- /* Check if we should test for optimisation */
- if (frame->virgin) {
- if (gif->bitmap_callbacks.bitmap_test_opaque) {
- frame->opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image);
- } else {
- frame->opaque = false;
- }
- frame->virgin = false;
- }
-
- if (gif->bitmap_callbacks.bitmap_set_opaque) {
- gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, frame->opaque);
- }
-
/* Restore the buffer position */
gif->buffer_position = save_buffer_position;
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=c17ab37852fadbc6fe...
commit c17ab37852fadbc6fe7ae80a969aa408f3d809b1
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Unify frame clearing functions.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index fb1c675..97d5b79 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -842,63 +842,50 @@ static inline gif_result gif__decode(
}
/**
- * Clear a gif frame.
+ * Restore a GIF to the background colour.
*
* \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to clear.
+ * \param[in] frame The frame to clear, or NULL.
* \param[in] bitmap The bitmap to clear the frame in.
*/
-static void gif_clear_frame(
+static void gif__restore_bg(
struct gif_animation *gif,
struct gif_frame *frame,
uint32_t *bitmap)
{
- uint32_t width;
- uint32_t height;
- uint32_t offset_x;
- uint32_t offset_y;
-
- assert(frame->disposal_method == GIF_DISPOSAL_RESTORE_BG);
-
- /* Ensure this frame is supposed to be decoded */
- if (frame->display == false) {
- return;
- }
+ if (frame == NULL) {
+ memset(bitmap, GIF_TRANSPARENT_COLOUR,
+ gif->width * gif->height * sizeof(*bitmap));
+ } else {
+ uint32_t offset_x = frame->redraw_x;
+ uint32_t offset_y = frame->redraw_y;
+ uint32_t width = frame->redraw_width;
+ uint32_t height = frame->redraw_height;
- offset_x = frame->redraw_x;
- offset_y = frame->redraw_y;
- width = frame->redraw_width;
- height = frame->redraw_height;
+ if (frame->display == false) {
+ return;
+ }
- /* Clear our frame */
- for (uint32_t y = 0; y < height; y++) {
- uint32_t *scanline;
- scanline = bitmap + offset_x + ((offset_y + y) * gif->width);
if (frame->transparency) {
- memset(scanline, GIF_TRANSPARENT_COLOUR, width * 4);
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ memset(scanline, GIF_TRANSPARENT_COLOUR,
+ width * sizeof(*bitmap));
+ }
} else {
- for (uint32_t x = 0; x < width; x++) {
- scanline[x] = gif->bg_colour;
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ for (uint32_t x = 0; x < width; x++) {
+ scanline[x] = gif->bg_colour;
+ }
}
}
}
}
/**
- * Wipe bitmap to transparent.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] bitmap The bitmap to wipe.
- */
-static inline void gif__wipe_bitmap(
- const struct gif_animation *gif,
- uint32_t *bitmap)
-{
- memset((char*)bitmap, GIF_TRANSPARENT_COLOUR,
- gif->width * gif->height * sizeof(*bitmap));
-}
-
-/**
* decode a gif frame
*
* \param gif gif animation context.
@@ -1008,13 +995,13 @@ gif_internal_decode_frame(gif_animation *gif,
/* Handle any bitmap clearing/restoration required before decoding this
* frame. */
if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
- gif__wipe_bitmap(gif, frame_data);
+ gif__restore_bg(gif, NULL, frame_data);
} else {
struct gif_frame *prev = &gif->frames[frame_idx - 1];
if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
- gif_clear_frame(gif, prev, frame_data);
+ gif__restore_bg(gif, prev, frame_data);
} else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
/*
@@ -1023,7 +1010,7 @@ gif_internal_decode_frame(gif_animation *gif,
*/
ret = gif__recover_frame(gif);
if (ret != GIF_OK) {
- gif__wipe_bitmap(gif, frame_data);
+ gif__restore_bg(gif, prev, frame_data);
}
}
}
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=0f7bcedeedff2cdee8...
commit 0f7bcedeedff2cdee82644213741b5b96d655d92
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Align disposal methods with spec.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 9001941..fb1c675 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -45,12 +45,15 @@
/** No transparency */
#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu)
-/* GIF Flags */
-#define GIF_FRAME_COMBINE 1
-#define GIF_FRAME_CLEAR 2
-#define GIF_FRAME_RESTORE 3
-#define GIF_FRAME_QUIRKS_RESTORE 4
+enum gif_disposal {
+ GIF_DISPOSAL_UNSPECIFIED,
+ GIF_DISPOSAL_NONE,
+ GIF_DISPOSAL_RESTORE_BG,
+ GIF_DISPOSAL_RESTORE_PREV,
+ GIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for GIF_DISPOSAL_RESTORE_PREV. */
+};
+/* GIF Flags */
#define GIF_INTERLACE_MASK 0x40
#define GIF_COLOUR_TABLE_MASK 0x80
#define GIF_COLOUR_TABLE_SIZE_MASK 0x07
@@ -138,15 +141,15 @@ static gif_result gif__parse_extension_graphic_control(
* believe some (older?) software may even actually
* export this way. We handle this as a type of
* "quirks" mode. */
- if (frame->disposal_method == GIF_FRAME_QUIRKS_RESTORE) {
- frame->disposal_method = GIF_FRAME_RESTORE;
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->disposal_method = GIF_DISPOSAL_RESTORE_PREV;
}
/* if we are clearing the background then we need to
* redraw enough to cover the previous frame too. */
frame->redraw_required =
- frame->disposal_method == GIF_FRAME_CLEAR ||
- frame->disposal_method == GIF_FRAME_RESTORE;
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_BG ||
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV;
return GIF_OK;
}
@@ -855,7 +858,7 @@ static void gif_clear_frame(
uint32_t offset_x;
uint32_t offset_y;
- assert(frame->disposal_method == GIF_FRAME_CLEAR);
+ assert(frame->disposal_method == GIF_DISPOSAL_RESTORE_BG);
/* Ensure this frame is supposed to be decoded */
if (frame->display == false) {
@@ -1010,10 +1013,10 @@ gif_internal_decode_frame(gif_animation *gif,
} else {
struct gif_frame *prev = &gif->frames[frame_idx - 1];
- if (prev->disposal_method == GIF_FRAME_CLEAR) {
+ if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
gif_clear_frame(gif, prev, frame_data);
- } else if (prev->disposal_method == GIF_FRAME_RESTORE) {
+ } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
/*
* If the previous frame's disposal method requires we
* restore the previous image, restore our saved image.
@@ -1025,7 +1028,7 @@ gif_internal_decode_frame(gif_animation *gif,
}
}
- if (frame->disposal_method == GIF_FRAME_RESTORE) {
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
/* Store the previous frame for later restoration */
gif__record_frame(gif);
}
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=22b5b8f627a770172f...
commit 22b5b8f627a770172fbcc9e37656dcf40836f5df
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Clear can't fail.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 973c592..9001941 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -844,9 +844,8 @@ static inline gif_result gif__decode(
* \param[in] gif The gif object we're decoding.
* \param[in] frame The frame to clear.
* \param[in] bitmap The bitmap to clear the frame in.
- * \return GIF_OK on success, appropriate error otherwise.
*/
-static gif_result gif_clear_frame(
+static void gif_clear_frame(
struct gif_animation *gif,
struct gif_frame *frame,
uint32_t *bitmap)
@@ -860,7 +859,7 @@ static gif_result gif_clear_frame(
/* Ensure this frame is supposed to be decoded */
if (frame->display == false) {
- return GIF_OK;
+ return;
}
offset_x = frame->redraw_x;
@@ -880,8 +879,6 @@ static gif_result gif_clear_frame(
}
}
}
-
- return GIF_OK;
}
/**
@@ -1014,10 +1011,8 @@ gif_internal_decode_frame(gif_animation *gif,
struct gif_frame *prev = &gif->frames[frame_idx - 1];
if (prev->disposal_method == GIF_FRAME_CLEAR) {
- ret = gif_clear_frame(gif, prev, frame_data);
- if (ret != GIF_OK) {
- goto gif_decode_frame_exit;
- }
+ gif_clear_frame(gif, prev, frame_data);
+
} else if (prev->disposal_method == GIF_FRAME_RESTORE) {
/*
* If the previous frame's disposal method requires we
commitdiff
http://git.netsurf-browser.org/libnsgif.git/commit/?id=1587b6a1a936ba11df...
commit 1587b6a1a936ba11dfc951d07766562dbb4e8cf9
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Helper to get the frame structure for given frame index.
Removes weird special casing of first frame.
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 6ee7ab3..973c592 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -408,6 +408,41 @@ static gif_result gif__parse_colour_table(
return GIF_OK;
}
+static struct gif_frame *gif__get_frame(
+ struct gif_animation *gif,
+ uint32_t frame_idx)
+{
+ struct gif_frame *frame;
+
+ if (gif->frame_holders > frame_idx) {
+ frame = &gif->frames[frame_idx];
+ } else {
+ /* Allocate more memory */
+ size_t count = frame_idx + 1;
+ struct gif_frame *temp;
+
+ temp = realloc(gif->frames, count * sizeof(*frame));
+ if (temp == NULL) {
+ return NULL;
+ }
+ gif->frames = temp;
+ gif->frame_holders = count;
+
+ frame = &gif->frames[frame_idx];
+
+ frame->transparency = false;
+ frame->transparency_index = GIF_NO_TRANSPARENCY;
+ frame->frame_pointer = gif->buffer_position;
+ frame->redraw_required = false;
+ frame->disposal_method = 0;
+ frame->frame_delay = 100;
+ frame->display = false;
+ frame->virgin = true;
+ }
+
+ return frame;
+}
+
/**
* Attempts to initialise the next frame
*
@@ -426,7 +461,7 @@ static gif_result gif_initialise_frame(gif_animation *gif,
uint32_t frame_idx)
{
gif_result ret;
- gif_frame *temp_buf;
+ struct gif_frame *frame;
uint8_t *gif_data, *gif_end;
int gif_bytes;
uint32_t block_size;
@@ -456,32 +491,9 @@ static gif_result gif_initialise_frame(gif_animation *gif,
return GIF_FRAME_DATA_ERROR;
}
- /* Get some memory to store our pointers in etc. */
- if (gif->frame_holders <= frame_idx) {
- /* Allocate more memory */
- temp_buf = (gif_frame *)realloc(gif->frames, (frame_idx + 1) * sizeof(gif_frame));
- if (temp_buf == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
- gif->frames = temp_buf;
- gif->frame_holders = frame_idx + 1;
- }
-
- /* Store our frame pointer. We would do it when allocating except we
- * start off with one frame allocated so we can always use realloc.
- */
- gif->frames[frame_idx].frame_pointer = gif->buffer_position;
- gif->frames[frame_idx].display = false;
- gif->frames[frame_idx].virgin = true;
- gif->frames[frame_idx].disposal_method = 0;
- gif->frames[frame_idx].transparency = false;
- gif->frames[frame_idx].transparency_index = GIF_NO_TRANSPARENCY;
- gif->frames[frame_idx].frame_delay = 100;
- gif->frames[frame_idx].redraw_required = false;
-
- /* Invalidate any previous decoding we have of this frame */
- if (gif->decoded_frame == (int)frame_idx) {
- gif->decoded_frame = GIF_INVALID_FRAME;
+ frame = gif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
}
/* We pretend to initialise the frames, but really we just skip over
@@ -914,7 +926,10 @@ gif_internal_decode_frame(gif_animation *gif,
return GIF_OK;
}
- frame = &gif->frames[frame_idx];
+ frame = gif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
/* Ensure this frame is supposed to be decoded */
if (frame->display == false) {
@@ -1098,6 +1113,7 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned
char *data)
*/
gif->frame_image = NULL;
gif->frames = NULL;
+ gif->frame_holders = 0;
gif->local_colour_table = NULL;
gif->global_colour_table = NULL;
@@ -1192,13 +1208,6 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned
char *data)
}
}
- /* Initialise enough workspace for a frame */
- if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) {
- gif_finalise(gif);
- return GIF_INSUFFICIENT_MEMORY;
- }
- gif->frame_holders = 1;
-
/* Remember we've done this now */
gif->buffer_position = gif_data - gif->gif_data;
}
-----------------------------------------------------------------------
Summary of changes:
src/libnsgif.c | 1527 +++++++++++++++++++++++++++++---------------------------
1 file changed, 785 insertions(+), 742 deletions(-)
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 6ee7ab3..1280798 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -45,12 +45,15 @@
/** No transparency */
#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu)
-/* GIF Flags */
-#define GIF_FRAME_COMBINE 1
-#define GIF_FRAME_CLEAR 2
-#define GIF_FRAME_RESTORE 3
-#define GIF_FRAME_QUIRKS_RESTORE 4
+enum gif_disposal {
+ GIF_DISPOSAL_UNSPECIFIED,
+ GIF_DISPOSAL_NONE,
+ GIF_DISPOSAL_RESTORE_BG,
+ GIF_DISPOSAL_RESTORE_PREV,
+ GIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for GIF_DISPOSAL_RESTORE_PREV. */
+};
+/* GIF Flags */
#define GIF_INTERLACE_MASK 0x40
#define GIF_COLOUR_TABLE_MASK 0x80
#define GIF_COLOUR_TABLE_SIZE_MASK 0x07
@@ -67,6 +70,21 @@
/** standard GIF header size */
#define GIF_STANDARD_HEADER_SIZE 13
+static gif_result gif_error_from_lzw(lzw_result l_res)
+{
+ static const gif_result g_res[] = {
+ [LZW_OK] = GIF_OK,
+ [LZW_OK_EOD] = GIF_END_OF_FRAME,
+ [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY,
+ [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA,
+ [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR,
+ [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR,
+ [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR,
+ };
+ assert(l_res != LZW_BAD_PARAM);
+ assert(l_res != LZW_NO_COLOUR);
+ return g_res[l_res];
+}
/**
* Updates the sprite memory size
@@ -76,10 +94,10 @@
* \param height The height of the sprite
* \return GIF_INSUFFICIENT_MEMORY for a memory error GIF_OK for success
*/
-static gif_result
-gif_initialise_sprite(gif_animation *gif,
- uint32_t width,
- uint32_t height)
+static gif_result gif_initialise_sprite(
+ struct gif_animation *gif,
+ uint32_t width,
+ uint32_t height)
{
/* Already allocated? */
if (gif->frame_image) {
@@ -96,794 +114,918 @@ gif_initialise_sprite(gif_animation *gif,
}
/**
- * Parse the application extension
+ * Helper to get the rendering bitmap for a gif.
*
- * \param[in] frame The gif object we're decoding.
- * \param[in] data The data to decode.
- * \param[in] len Byte length of data.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \param[in] gif The gif object we're decoding.
+ * \return Client pixel buffer for rendering into.
*/
-static gif_result gif__parse_extension_graphic_control(
- struct gif_frame *frame,
- uint8_t *data,
- size_t len)
+static inline uint32_t* gif__bitmap_get(
+ struct gif_animation *gif)
{
- /* 6-byte Graphic Control Extension is:
- *
- * +0 CHAR Graphic Control Label
- * +1 CHAR Block Size
- * +2 CHAR __Packed Fields__
- * 3BITS Reserved
- * 3BITS Disposal Method
- * 1BIT User Input Flag
- * 1BIT Transparent Color Flag
- * +3 SHORT Delay Time
- * +5 CHAR Transparent Color Index
- */
- if (len < 6) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- frame->frame_delay = data[3] | (data[4] << 8);
- if (data[2] & GIF_TRANSPARENCY_MASK) {
- frame->transparency = true;
- frame->transparency_index = data[5];
- }
+ gif_result ret;
- frame->disposal_method = ((data[2] & GIF_DISPOSAL_MASK) >> 2);
- /* I have encountered documentation and GIFs in the
- * wild that use 0x04 to restore the previous frame,
- * rather than the officially documented 0x03. I
- * believe some (older?) software may even actually
- * export this way. We handle this as a type of
- * "quirks" mode. */
- if (frame->disposal_method == GIF_FRAME_QUIRKS_RESTORE) {
- frame->disposal_method = GIF_FRAME_RESTORE;
+ /* Make sure we have a buffer to decode to. */
+ ret = gif_initialise_sprite(gif, gif->width, gif->height);
+ if (ret != GIF_OK) {
+ return NULL;
}
- /* if we are clearing the background then we need to
- * redraw enough to cover the previous frame too. */
- frame->redraw_required =
- frame->disposal_method == GIF_FRAME_CLEAR ||
- frame->disposal_method == GIF_FRAME_RESTORE;
-
- return GIF_OK;
+ /* Get the frame data */
+ assert(gif->bitmap_callbacks.bitmap_get_buffer);
+ return (uint32_t *)gif->bitmap_callbacks.bitmap_get_buffer(
+ gif->frame_image);
}
/**
- * Parse the application extension
+ * Helper to tell the client that their bitmap was modified.
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] data The data to decode.
- * \param[in] len Byte length of data.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \param[in] gif The gif object we're decoding.
*/
-static gif_result gif__parse_extension_application(
- struct gif_animation *gif,
- uint8_t *data,
- size_t len)
+static inline void gif__bitmap_modified(
+ const struct gif_animation *gif)
{
- /* 14-byte+ Application Extension is:
- *
- * +0 CHAR Application Extension Label
- * +1 CHAR Block Size
- * +2 8CHARS Application Identifier
- * +10 3CHARS Appl. Authentication Code
- * +13 1-256 Application Data (Data sub-blocks)
- */
- if (len < 17) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ if (gif->bitmap_callbacks.bitmap_modified) {
+ gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
}
+}
- if ((data[1] == 0x0b) &&
- (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) &&
- (data[13] == 0x03) && (data[14] == 0x01)) {
- gif->loop_count = data[15] | (data[16] << 8);
+/**
+ * Helper to tell the client that whether the bitmap is opaque.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frmae The frame that has been decoded.
+ */
+static inline void gif__bitmap_set_opaque(
+ const struct gif_animation *gif,
+ const struct gif_frame *frame)
+{
+ if (gif->bitmap_callbacks.bitmap_set_opaque) {
+ gif->bitmap_callbacks.bitmap_set_opaque(
+ gif->frame_image, frame->opaque);
}
-
- return GIF_OK;
}
/**
- * Parse the frame's extensions
+ * Helper to get the client to determine if the bitmap is opaque.
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to parse extensions for.
- * \param[in] decode Whether to decode or skip over the extension.
- * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
- * GIF_OK for success.
+ * \todo: We don't really need to get the client to do this for us.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \return true if the bitmap is opaque, false otherwise.
*/
-static gif_result gif__parse_frame_extensions(
- struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+static inline bool gif__bitmap_get_opaque(
+ const struct gif_animation *gif)
{
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
+ if (gif->bitmap_callbacks.bitmap_test_opaque) {
+ return gif->bitmap_callbacks.bitmap_test_opaque(
+ gif->frame_image);
+ }
- /* Get our buffer position etc. */
- gif_data = gif->gif_data + gif->buffer_position;
- gif_end = gif->gif_data + gif->buffer_size;
- gif_bytes = gif_end - gif_data;
+ return false;
+}
- /* Initialise the extensions */
- while (gif_bytes > 0 && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
- bool block_step = true;
- gif_result ret;
+static void gif__record_frame(
+ struct gif_animation *gif,
+ const uint32_t *bitmap)
+{
+ bool need_alloc = gif->prev_frame == NULL;
+ uint32_t *prev_frame;
- gif_data++;
- gif_bytes--;
+ if (gif->decoded_frame == GIF_INVALID_FRAME ||
+ gif->decoded_frame == gif->prev_index) {
+ /* No frame to copy, or already have this frame recorded. */
+ return;
+ }
- if (gif_bytes == 0) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
+ bitmap = gif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return;
+ }
- /* Switch on extension label */
- switch (gif_data[0]) {
- case GIF_EXTENSION_GRAPHIC_CONTROL:
- if (decode) {
- ret = gif__parse_extension_graphic_control(
- frame, gif_data, gif_bytes);
- if (ret != GIF_OK) {
- return ret;
- }
- }
- break;
+ if (gif->prev_frame != NULL &&
+ gif->width * gif->height > gif->prev_width * gif->prev_height) {
+ need_alloc = true;
+ }
- case GIF_EXTENSION_APPLICATION:
- if (decode) {
- ret = gif__parse_extension_application(
- gif, gif_data, gif_bytes);
- if (ret != GIF_OK) {
- return ret;
- }
- }
- break;
+ 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;
+ }
- case GIF_EXTENSION_COMMENT:
- /* Move the pointer to the first data sub-block Skip 1
- * byte for the extension label. */
- ++gif_data;
- block_step = false;
- break;
+ memcpy(prev_frame, bitmap, gif->width * gif->height * 4);
- default:
- break;
- }
+ gif->prev_frame = prev_frame;
+ gif->prev_width = gif->width;
+ gif->prev_height = gif->height;
+ gif->prev_index = gif->decoded_frame;
+}
- if (block_step) {
- /* Move the pointer to the first data sub-block Skip 2
- * bytes for the extension label and size fields Skip
- * the extension size itself
- */
- if (gif_bytes < 2) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- gif_data += 2 + gif_data[1];
- }
+static gif_result gif__recover_frame(
+ const struct gif_animation *gif,
+ uint32_t *bitmap)
+{
+ 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;
- /* Repeatedly skip blocks until we get a zero block or run out
- * of data. This data is ignored by this gif decoder. */
- while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
- gif_data += gif_data[0] + 1;
- if (gif_data >= gif_end) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- }
- gif_data++;
- gif_bytes = gif_end - gif_data;
+ if (prev_frame == NULL) {
+ return GIF_FRAME_DATA_ERROR;
}
- if (gif_data > gif_end) {
- gif_data = gif_end;
+ for (unsigned y = 0; y < height; y++) {
+ memcpy(bitmap, prev_frame, width * 4);
+
+ bitmap += gif->width;
+ prev_frame += gif->prev_width;
}
- /* Set buffer position and return */
- gif->buffer_position = gif_data - gif->gif_data;
return GIF_OK;
}
-/**
- * Parse a GIF Image Descriptor.
- *
- * The format is:
- *
- * +0 CHAR Image Separator (0x2c)
- * +1 SHORT Image Left Position
- * +3 SHORT Image Top Position
- * +5 SHORT Width
- * +7 SHORT Height
- * +9 CHAR __Packed Fields__
- * 1BIT Local Colour Table Flag
- * 1BIT Interlace Flag
- * 1BIT Sort Flag
- * 2BITS Reserved
- * 3BITS Size of Local Colour Table
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to parse an image descriptor for.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_image_descriptor(
- struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+static uint32_t gif_interlaced_line(int height, int y)
{
- const uint8_t *data = gif->gif_data + gif->buffer_position;
- size_t len = gif->buffer_size - gif->buffer_position;
- enum {
- GIF_IMAGE_DESCRIPTOR_LEN = 10u,
- GIF_IMAGE_SEPARATOR = 0x2Cu,
- };
-
- assert(gif != NULL);
- assert(frame != NULL);
+ if ((y << 3) < height) {
+ return (y << 3);
+ }
+ y -= ((height + 7) >> 3);
+ if ((y << 3) < (height - 4)) {
+ return (y << 3) + 4;
+ }
+ y -= ((height + 3) >> 3);
+ if ((y << 2) < (height - 2)) {
+ return (y << 2) + 2;
+ }
+ y -= ((height + 1) >> 2);
+ return (y << 1) + 1;
+}
- if (len < GIF_IMAGE_DESCRIPTOR_LEN) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+static gif_result gif__decode_complex(
+ struct gif_animation *gif,
+ uint32_t width,
+ uint32_t height,
+ uint32_t offset_x,
+ uint32_t offset_y,
+ uint32_t interlace,
+ uint8_t minimum_code_size,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
+{
+ uint32_t available = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
+
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init(gif->lzw_ctx, minimum_code_size,
+ gif->gif_data, gif->buffer_size, gif->buffer_position);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
}
- if (decode) {
- unsigned x, y, w, h;
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t x;
+ uint32_t decode_y;
+ uint32_t *frame_scanline;
- if (data[0] != GIF_IMAGE_SEPARATOR) {
- return GIF_FRAME_DATA_ERROR;
+ if (interlace) {
+ decode_y = gif_interlaced_line(height, y) + offset_y;
+ } else {
+ decode_y = y + offset_y;
}
+ frame_scanline = frame_data + offset_x + (decode_y * gif->width);
- x = data[1] | (data[2] << 8);
- y = data[3] | (data[4] << 8);
- w = data[5] | (data[6] << 8);
- h = data[7] | (data[8] << 8);
- frame->flags = data[9];
-
- frame->redraw_x = x;
- frame->redraw_y = y;
- frame->redraw_width = w;
- frame->redraw_height = h;
+ x = width;
+ while (x > 0) {
+ const uint8_t *uncompressed;
+ unsigned row_available;
+ if (available == 0) {
+ if (res != LZW_OK) {
+ /* Unexpected end of frame, try to recover */
+ if (res == LZW_OK_EOD) {
+ ret = GIF_OK;
+ } else {
+ ret = gif_error_from_lzw(res);
+ }
+ break;
+ }
+ res = lzw_decode(gif->lzw_ctx,
+ &uncompressed, &available);
+ }
- /* Frame size may have grown. */
- gif->width = (x + w > gif->width ) ? x + w : gif->width;
- gif->height = (y + h > gif->height) ? y + h : gif->height;
+ row_available = x < available ? x : available;
+ x -= row_available;
+ available -= row_available;
+ if (transparency_index > 0xFF) {
+ while (row_available-- > 0) {
+ *frame_scanline++ =
+ colour_table[*uncompressed++];
+ }
+ } else {
+ while (row_available-- > 0) {
+ register uint32_t colour;
+ colour = *uncompressed++;
+ if (colour != transparency_index) {
+ *frame_scanline =
+ colour_table[colour];
+ }
+ frame_scanline++;
+ }
+ }
+ }
}
-
- gif->buffer_position += GIF_IMAGE_DESCRIPTOR_LEN;
- return GIF_OK;
+ return ret;
}
-/**
- * Get a frame's colour table.
- *
- * Sets up gif->colour_table for the frame.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to get the colour table for.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_colour_table(
+static gif_result gif__decode_simple(
struct gif_animation *gif,
- struct gif_frame *frame,
- bool decode)
+ uint32_t height,
+ uint32_t offset_y,
+ uint8_t minimum_code_size,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
{
- const uint8_t *data = gif->gif_data + gif->buffer_position;
- size_t len = gif->buffer_size - gif->buffer_position;
- unsigned colour_table_size;
+ uint32_t pixels = gif->width * height;
+ uint32_t written = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
- assert(gif != NULL);
- assert(frame != NULL);
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init_map(gif->lzw_ctx,
+ minimum_code_size, transparency_index, colour_table,
+ gif->gif_data, gif->buffer_size, gif->buffer_position);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
+ }
- if ((frame->flags & GIF_COLOUR_TABLE_MASK) == 0) {
- gif->colour_table = gif->global_colour_table;
- return GIF_OK;
+ frame_data += (offset_y * gif->width);
+
+ while (pixels > 0) {
+ res = lzw_decode_map(gif->lzw_ctx,
+ frame_data, pixels, &written);
+ pixels -= written;
+ frame_data += written;
+ if (res != LZW_OK) {
+ /* Unexpected end of frame, try to recover */
+ if (res == LZW_OK_EOD) {
+ ret = GIF_OK;
+ } else {
+ ret = gif_error_from_lzw(res);
+ }
+ break;
+ }
}
- colour_table_size = 2 << (frame->flags & GIF_COLOUR_TABLE_SIZE_MASK);
- if (len < colour_table_size * 3) {
- return GIF_INSUFFICIENT_FRAME_DATA;
+ if (pixels == 0) {
+ ret = GIF_OK;
}
- if (decode) {
- int count = colour_table_size;
- uint8_t *entry = (uint8_t *)gif->local_colour_table;
+ return ret;
+}
- while (count--) {
- /* Gif colour map contents are r,g,b.
- *
- * We want to pack them bytewise into the
- * colour table, such that the red component
- * is in byte 0 and the alpha component is in
- * byte 3.
- */
+static inline gif_result gif__decode(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint8_t minimum_code_size,
+ uint32_t *restrict frame_data)
+{
+ gif_result ret;
+ uint32_t offset_x = frame->redraw_x;
+ uint32_t offset_y = frame->redraw_y;
+ uint32_t width = frame->redraw_width;
+ uint32_t height = frame->redraw_height;
+ uint32_t interlace = frame->flags & GIF_INTERLACE_MASK;
+ uint32_t transparency_index = frame->transparency_index;
+ uint32_t *restrict colour_table = gif->colour_table;
- *entry++ = *data++; /* r */
- *entry++ = *data++; /* g */
- *entry++ = *data++; /* b */
- *entry++ = 0xff; /* a */
- }
+ if (interlace == false && width == gif->width && offset_x == 0) {
+ ret = gif__decode_simple(gif, height, offset_y,
+ minimum_code_size, transparency_index,
+ frame_data, colour_table);
+ } else {
+ ret = gif__decode_complex(gif, width, height,
+ offset_x, offset_y, interlace,
+ minimum_code_size, transparency_index,
+ frame_data, colour_table);
}
- gif->buffer_position += colour_table_size * 3;
- gif->colour_table = gif->local_colour_table;
- return GIF_OK;
+ return ret;
}
/**
- * Attempts to initialise the next frame
+ * Restore a GIF to the background colour.
*
- * \param[in] gif The animation context
- * \param[in] frame_idx The frame number to decode.
- * \return error code
- * - GIF_INSUFFICIENT_DATA for insufficient data to do anything
- * - GIF_FRAME_DATA_ERROR for GIF frame data error
- * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
- * - GIF_DATA_ERROR for GIF error (invalid frame header)
- * - GIF_OK for successful decoding
- * - GIF_WORKING for successful decoding if more frames are expected
-*/
-static gif_result gif_initialise_frame(gif_animation *gif,
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to clear, or NULL.
+ * \param[in] bitmap The bitmap to clear the frame in.
+ */
+static void gif__restore_bg(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint32_t *bitmap)
+{
+ if (frame == NULL) {
+ memset(bitmap, GIF_TRANSPARENT_COLOUR,
+ gif->width * gif->height * sizeof(*bitmap));
+ } else {
+ uint32_t offset_x = frame->redraw_x;
+ uint32_t offset_y = frame->redraw_y;
+ uint32_t width = frame->redraw_width;
+ uint32_t height = frame->redraw_height;
+
+ if (frame->display == false) {
+ return;
+ }
+
+ if (frame->transparency) {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ memset(scanline, GIF_TRANSPARENT_COLOUR,
+ width * sizeof(*bitmap));
+ }
+ } else {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->width;
+ for (uint32_t x = 0; x < width; x++) {
+ scanline[x] = gif->bg_colour;
+ }
+ }
+ }
+ }
+}
+
+static gif_result gif__update_bitmap(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ uint8_t minimum_code_size,
uint32_t frame_idx)
{
gif_result ret;
- gif_frame *temp_buf;
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
- uint32_t block_size;
+ uint32_t *bitmap;
- /* Get our buffer position etc. */
- gif_data = (uint8_t *)(gif->gif_data + gif->buffer_position);
- gif_end = (uint8_t *)(gif->gif_data + gif->buffer_size);
- gif_bytes = (gif_end - gif_data);
+ gif->decoded_frame = frame_idx;
- /* Check if we've finished */
- if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) {
- return GIF_OK;
+ bitmap = gif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
}
- /* Check if there is enough data remaining. The shortest block of data
- * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif
- * trailer
- */
- if (gif_bytes < 6) {
- return GIF_INSUFFICIENT_DATA;
- }
+ /* Handle any bitmap clearing/restoration required before decoding this
+ * frame. */
+ if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
+ gif__restore_bg(gif, NULL, bitmap);
- /* We could theoretically get some junk data that gives us millions of
- * frames, so we ensure that we don't have a silly number
- */
- if (frame_idx > 4096) {
- return GIF_FRAME_DATA_ERROR;
- }
+ } else {
+ struct gif_frame *prev = &gif->frames[frame_idx - 1];
- /* Get some memory to store our pointers in etc. */
- if (gif->frame_holders <= frame_idx) {
- /* Allocate more memory */
- temp_buf = (gif_frame *)realloc(gif->frames, (frame_idx + 1) * sizeof(gif_frame));
- if (temp_buf == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
+ if (prev->disposal_method == GIF_DISPOSAL_RESTORE_BG) {
+ gif__restore_bg(gif, prev, bitmap);
+
+ } else if (prev->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ ret = gif__recover_frame(gif, bitmap);
+ if (ret != GIF_OK) {
+ gif__restore_bg(gif, prev, bitmap);
+ }
}
- gif->frames = temp_buf;
- gif->frame_holders = frame_idx + 1;
}
- /* Store our frame pointer. We would do it when allocating except we
- * start off with one frame allocated so we can always use realloc.
- */
- gif->frames[frame_idx].frame_pointer = gif->buffer_position;
- gif->frames[frame_idx].display = false;
- gif->frames[frame_idx].virgin = true;
- gif->frames[frame_idx].disposal_method = 0;
- gif->frames[frame_idx].transparency = false;
- gif->frames[frame_idx].transparency_index = GIF_NO_TRANSPARENCY;
- gif->frames[frame_idx].frame_delay = 100;
- gif->frames[frame_idx].redraw_required = false;
-
- /* Invalidate any previous decoding we have of this frame */
- if (gif->decoded_frame == (int)frame_idx) {
- gif->decoded_frame = GIF_INVALID_FRAME;
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV) {
+ /* Store the previous frame for later restoration */
+ gif__record_frame(gif, bitmap);
}
- /* We pretend to initialise the frames, but really we just skip over
- * all the data contained within. This is all basically a cut down
- * version of gif_decode_frame that doesn't have any of the LZW bits in
- * it.
- */
+ ret = gif__decode(gif, frame, minimum_code_size, bitmap);
- /* Initialise any extensions */
- gif->buffer_position = gif_data - gif->gif_data;
- ret = gif__parse_frame_extensions(gif, &gif->frames[frame_idx], true);
- if (ret != GIF_OK) {
- return ret;
+ gif__bitmap_modified(gif);
+
+ if (frame->virgin) {
+ frame->opaque = gif__bitmap_get_opaque(gif);
+ frame->virgin = false;
}
+ gif__bitmap_set_opaque(gif, frame);
- ret = gif__parse_image_descriptor(gif, &gif->frames[frame_idx], true);
- if (ret != GIF_OK) {
- return ret;
+ return ret;
+}
+
+/**
+ * Parse the application extension
+ *
+ * \param[in] frame The gif object we're decoding.
+ * \param[in] data The data to decode.
+ * \param[in] len Byte length of data.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_extension_graphic_control(
+ struct gif_frame *frame,
+ uint8_t *data,
+ size_t len)
+{
+ /* 6-byte Graphic Control Extension is:
+ *
+ * +0 CHAR Graphic Control Label
+ * +1 CHAR Block Size
+ * +2 CHAR __Packed Fields__
+ * 3BITS Reserved
+ * 3BITS Disposal Method
+ * 1BIT User Input Flag
+ * 1BIT Transparent Color Flag
+ * +3 SHORT Delay Time
+ * +5 CHAR Transparent Color Index
+ */
+ if (len < 6) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
}
- ret = gif__parse_colour_table(gif, &gif->frames[frame_idx], false);
- if (ret != GIF_OK) {
- return ret;
+ frame->frame_delay = data[3] | (data[4] << 8);
+ if (data[2] & GIF_TRANSPARENCY_MASK) {
+ frame->transparency = true;
+ frame->transparency_index = data[5];
}
- gif_data = gif->gif_data + gif->buffer_position;
- gif_bytes = (gif_end - gif_data);
- /* Move our data onwards and remember we've got a bit of this frame */
- gif->frame_count_partial = frame_idx + 1;
+ frame->disposal_method = ((data[2] & GIF_DISPOSAL_MASK) >> 2);
+ /* I have encountered documentation and GIFs in the
+ * wild that use 0x04 to restore the previous frame,
+ * rather than the officially documented 0x03. I
+ * believe some (older?) software may even actually
+ * export this way. We handle this as a type of
+ * "quirks" mode. */
+ if (frame->disposal_method == GIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->disposal_method = GIF_DISPOSAL_RESTORE_PREV;
+ }
- /* Ensure we have a correct code size */
- if (gif_bytes < 1) {
+ /* if we are clearing the background then we need to
+ * redraw enough to cover the previous frame too. */
+ frame->redraw_required =
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_BG ||
+ frame->disposal_method == GIF_DISPOSAL_RESTORE_PREV;
+
+ return GIF_OK;
+}
+
+/**
+ * Parse the application extension
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] data The data to decode.
+ * \param[in] len Byte length of data.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_extension_application(
+ struct gif_animation *gif,
+ uint8_t *data,
+ size_t len)
+{
+ /* 14-byte+ Application Extension is:
+ *
+ * +0 CHAR Application Extension Label
+ * +1 CHAR Block Size
+ * +2 8CHARS Application Identifier
+ * +10 3CHARS Appl. Authentication Code
+ * +13 1-256 Application Data (Data sub-blocks)
+ */
+ if (len < 17) {
return GIF_INSUFFICIENT_FRAME_DATA;
}
- if (gif_data[0] >= LZW_CODE_MAX) {
- return GIF_DATA_ERROR;
+
+ if ((data[1] == 0x0b) &&
+ (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) &&
+ (data[13] == 0x03) && (data[14] == 0x01)) {
+ gif->loop_count = data[15] | (data[16] << 8);
}
- /* Move our pointer to the actual image data */
- gif_data++;
- --gif_bytes;
+ return GIF_OK;
+}
+
+/**
+ * Parse the frame's extensions
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to parse extensions for.
+ * \param[in] decode Whether to decode or skip over the extension.
+ * \return GIF_INSUFFICIENT_FRAME_DATA if more data is needed,
+ * GIF_OK for success.
+ */
+static gif_result gif__parse_frame_extensions(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ bool decode)
+{
+ uint8_t *gif_data, *gif_end;
+ int gif_bytes;
+
+ /* Get our buffer position etc. */
+ gif_data = gif->gif_data + gif->buffer_position;
+ gif_end = gif->gif_data + gif->buffer_size;
+ gif_bytes = gif_end - gif_data;
+
+ /* Initialise the extensions */
+ while (gif_bytes > 0 && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
+ bool block_step = true;
+ gif_result ret;
+
+ gif_data++;
+ gif_bytes--;
+
+ if (gif_bytes == 0) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ /* Switch on extension label */
+ switch (gif_data[0]) {
+ case GIF_EXTENSION_GRAPHIC_CONTROL:
+ if (decode) {
+ ret = gif__parse_extension_graphic_control(
+ frame, gif_data, gif_bytes);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+ }
+ break;
+
+ case GIF_EXTENSION_APPLICATION:
+ if (decode) {
+ ret = gif__parse_extension_application(
+ gif, gif_data, gif_bytes);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+ }
+ break;
+
+ case GIF_EXTENSION_COMMENT:
+ /* Move the pointer to the first data sub-block Skip 1
+ * byte for the extension label. */
+ ++gif_data;
+ block_step = false;
+ break;
- /* Repeatedly skip blocks until we get a zero block or run out of data
- * These blocks of image data are processed later by gif_decode_frame()
- */
- block_size = 0;
- while (block_size != 1) {
- if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA;
- block_size = gif_data[0] + 1;
- /* Check if the frame data runs off the end of the file */
- if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
+ default:
+ break;
+ }
+
+ if (block_step) {
+ /* Move the pointer to the first data sub-block Skip 2
+ * bytes for the extension label and size fields Skip
+ * the extension size itself
*/
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
- } else {
+ if (gif_bytes < 2) {
return GIF_INSUFFICIENT_FRAME_DATA;
}
- } else {
- gif_bytes -= block_size;
- gif_data += block_size;
+ gif_data += 2 + gif_data[1];
}
- }
-
- /* Add the frame and set the display flag */
- gif->buffer_position = gif_data - gif->gif_data;
- gif->frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
- /* Check if we've finished */
- if (gif_bytes < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- } else {
- if (gif_data[0] == GIF_TRAILER) {
- return GIF_OK;
+ /* Repeatedly skip blocks until we get a zero block or run out
+ * of data. This data is ignored by this gif decoder. */
+ while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
+ gif_data += gif_data[0] + 1;
+ if (gif_data >= gif_end) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
}
+ gif_data++;
+ gif_bytes = gif_end - gif_data;
}
- return GIF_WORKING;
-}
-static uint32_t gif_interlaced_line(int height, int y)
-{
- if ((y << 3) < height) {
- return (y << 3);
- }
- y -= ((height + 7) >> 3);
- if ((y << 3) < (height - 4)) {
- return (y << 3) + 4;
- }
- y -= ((height + 3) >> 3);
- if ((y << 2) < (height - 2)) {
- return (y << 2) + 2;
+ if (gif_data > gif_end) {
+ gif_data = gif_end;
}
- y -= ((height + 1) >> 2);
- return (y << 1) + 1;
-}
-
-static gif_result gif_error_from_lzw(lzw_result l_res)
-{
- static const gif_result g_res[] = {
- [LZW_OK] = GIF_OK,
- [LZW_OK_EOD] = GIF_END_OF_FRAME,
- [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY,
- [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA,
- [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR,
- [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR,
- [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR,
- };
- assert(l_res != LZW_BAD_PARAM);
- assert(l_res != LZW_NO_COLOUR);
- return g_res[l_res];
+ /* Set buffer position and return */
+ gif->buffer_position = gif_data - gif->gif_data;
+ return GIF_OK;
}
-static void gif__record_frame(gif_animation *gif)
+/**
+ * Parse a GIF Image Descriptor.
+ *
+ * The format is:
+ *
+ * +0 CHAR Image Separator (0x2c)
+ * +1 SHORT Image Left Position
+ * +3 SHORT Image Top Position
+ * +5 SHORT Width
+ * +7 SHORT Height
+ * +9 CHAR __Packed Fields__
+ * 1BIT Local Colour Table Flag
+ * 1BIT Interlace Flag
+ * 1BIT Sort Flag
+ * 2BITS Reserved
+ * 3BITS Size of Local Colour Table
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to parse an image descriptor for.
+ * \return GIF_OK on success, appropriate error otherwise.
+ */
+static gif_result gif__parse_image_descriptor(
+ struct gif_animation *gif,
+ struct gif_frame *frame,
+ bool decode)
{
- bool need_alloc = gif->prev_frame == NULL;
- const uint32_t *frame_data;
- uint32_t *prev_frame;
+ const uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ enum {
+ GIF_IMAGE_DESCRIPTOR_LEN = 10u,
+ GIF_IMAGE_SEPARATOR = 0x2Cu,
+ };
- 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 != NULL);
+ assert(frame != NULL);
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
- return;
+ if (len < GIF_IMAGE_DESCRIPTOR_LEN) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
}
- if (gif->prev_frame != NULL &&
- gif->width * gif->height > gif->prev_width * gif->prev_height) {
- need_alloc = true;
- }
+ if (decode) {
+ unsigned x, y, w, h;
- if (need_alloc) {
- prev_frame = realloc(gif->prev_frame,
- gif->width * gif->height * 4);
- if (prev_frame == NULL) {
- return;
+ if (data[0] != GIF_IMAGE_SEPARATOR) {
+ return GIF_FRAME_DATA_ERROR;
}
- } 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_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;
- }
+ x = data[1] | (data[2] << 8);
+ y = data[3] | (data[4] << 8);
+ w = data[5] | (data[6] << 8);
+ h = data[7] | (data[8] << 8);
+ frame->flags = data[9];
- for (unsigned y = 0; y < height; y++) {
- memcpy(frame_data, prev_frame, width * 4);
+ frame->redraw_x = x;
+ frame->redraw_y = y;
+ frame->redraw_width = w;
+ frame->redraw_height = h;
- frame_data += gif->width;
- prev_frame += gif->prev_width;
+ /* Frame size may have grown. */
+ gif->width = (x + w > gif->width ) ? x + w : gif->width;
+ gif->height = (y + h > gif->height) ? y + h : gif->height;
}
+ gif->buffer_position += GIF_IMAGE_DESCRIPTOR_LEN;
return GIF_OK;
}
-static gif_result
-gif__decode_complex(
+/**
+ * Get a frame's colour table.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to get the colour table for.
+ * \return GIF_OK on success, appropriate error otherwise.
+ */
+static gif_result gif__parse_colour_table(
struct gif_animation *gif,
- uint32_t width,
- uint32_t height,
- uint32_t offset_x,
- uint32_t offset_y,
- uint32_t interlace,
- uint8_t minimum_code_size,
- uint32_t transparency_index,
- uint32_t *restrict frame_data,
- uint32_t *restrict colour_table)
+ struct gif_frame *frame,
+ bool decode)
{
- uint32_t available = 0;
- gif_result ret = GIF_OK;
- lzw_result res;
-
- /* Initialise the LZW decoding */
- res = lzw_decode_init(gif->lzw_ctx, minimum_code_size,
- gif->gif_data, gif->buffer_size, gif->buffer_position);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
- }
+ const uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ unsigned colour_table_size;
- for (uint32_t y = 0; y < height; y++) {
- uint32_t x;
- uint32_t decode_y;
- uint32_t *frame_scanline;
+ assert(gif != NULL);
+ assert(frame != NULL);
- if (interlace) {
- decode_y = gif_interlaced_line(height, y) + offset_y;
- } else {
- decode_y = y + offset_y;
- }
- frame_scanline = frame_data + offset_x + (decode_y * gif->width);
+ if ((frame->flags & GIF_COLOUR_TABLE_MASK) == 0) {
+ gif->colour_table = gif->global_colour_table;
+ return GIF_OK;
+ }
- x = width;
- while (x > 0) {
- const uint8_t *uncompressed;
- unsigned row_available;
- if (available == 0) {
- if (res != LZW_OK) {
- /* Unexpected end of frame, try to recover */
- if (res == LZW_OK_EOD) {
- ret = GIF_OK;
- } else {
- ret = gif_error_from_lzw(res);
- }
- break;
- }
- res = lzw_decode(gif->lzw_ctx,
- &uncompressed, &available);
- }
+ colour_table_size = 2 << (frame->flags & GIF_COLOUR_TABLE_SIZE_MASK);
+ if (len < colour_table_size * 3) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
- row_available = x < available ? x : available;
- x -= row_available;
- available -= row_available;
- if (transparency_index > 0xFF) {
- while (row_available-- > 0) {
- *frame_scanline++ =
- colour_table[*uncompressed++];
- }
- } else {
- while (row_available-- > 0) {
- register uint32_t colour;
- colour = *uncompressed++;
- if (colour != transparency_index) {
- *frame_scanline =
- colour_table[colour];
- }
- frame_scanline++;
- }
- }
+ if (decode) {
+ int count = colour_table_size;
+ uint8_t *entry = (uint8_t *)gif->local_colour_table;
+
+ while (count--) {
+ /* Gif colour map contents are r,g,b.
+ *
+ * We want to pack them bytewise into the
+ * colour table, such that the red component
+ * is in byte 0 and the alpha component is in
+ * byte 3.
+ */
+
+ *entry++ = *data++; /* r */
+ *entry++ = *data++; /* g */
+ *entry++ = *data++; /* b */
+ *entry++ = 0xff; /* a */
}
}
- return ret;
+
+ gif->buffer_position += colour_table_size * 3;
+ gif->colour_table = gif->local_colour_table;
+ return GIF_OK;
}
-static gif_result
-gif__decode_simple(
+/**
+ * Parse the image data for a gif frame.
+ *
+ * Sets up gif->colour_table for the frame.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The frame to get the colour table for.
+ * \return GIF_OK on success, appropriate error otherwise.
+ */
+static gif_result gif__parse_image_data(
struct gif_animation *gif,
- uint32_t height,
- uint32_t offset_y,
- uint8_t minimum_code_size,
- uint32_t transparency_index,
- uint32_t *restrict frame_data,
- uint32_t *restrict colour_table)
+ struct gif_frame *frame,
+ bool decode)
{
- uint32_t pixels = gif->width * height;
- uint32_t written = 0;
- gif_result ret = GIF_OK;
- lzw_result res;
+ uint8_t *data = gif->gif_data + gif->buffer_position;
+ size_t len = gif->buffer_size - gif->buffer_position;
+ uint32_t frame_idx = frame - gif->frames;
+ uint8_t minimum_code_size;
+ gif_result ret;
- /* Initialise the LZW decoding */
- res = lzw_decode_init_map(gif->lzw_ctx,
- minimum_code_size, transparency_index, colour_table,
- gif->gif_data, gif->buffer_size, gif->buffer_position);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
+ assert(gif != NULL);
+ assert(frame != NULL);
+
+ if (!decode) {
+ gif->frame_count_partial = frame_idx + 1;
}
- frame_data += (offset_y * gif->width);
+ /* Ensure sufficient data remains. A gif trailer or a minimum lzw code
+ * followed by a gif trailer is treated as OK, although without any
+ * image data. */
+ switch (len) {
+ case 2: if (data[1] == GIF_TRAILER) return GIF_OK;
+ /* Fall through. */
+ case 1: if (data[0] == GIF_TRAILER) return GIF_OK;
+ /* Fall through. */
+ case 0: return GIF_INSUFFICIENT_FRAME_DATA;
+ }
- while (pixels > 0) {
- res = lzw_decode_map(gif->lzw_ctx,
- frame_data, pixels, &written);
- pixels -= written;
- frame_data += written;
- if (res != LZW_OK) {
- /* Unexpected end of frame, try to recover */
- if (res == LZW_OK_EOD) {
- ret = GIF_OK;
+ minimum_code_size = data[0];
+ if (minimum_code_size >= LZW_CODE_MAX) {
+ return GIF_DATA_ERROR;
+ }
+ gif->buffer_position++;
+ data++;
+ len--;
+
+ if (decode) {
+ ret = gif__update_bitmap(gif, frame, minimum_code_size,
+ frame_idx);
+ } else {
+ uint32_t block_size = 0;
+
+ while (block_size != 1) {
+ if (len < 1) return GIF_INSUFFICIENT_FRAME_DATA;
+ block_size = data[0] + 1;
+ /* Check if the frame data runs off the end of the file */
+ if ((int)(len - block_size) < 0) {
+ /* Try to recover by signaling the end of the gif.
+ * Once we get garbage data, there is no logical way to
+ * determine where the next frame is. It's probably
+ * better to partially load the gif than not at all.
+ */
+ if (len >= 2) {
+ data[0] = 0;
+ data[1] = GIF_TRAILER;
+ len = 1;
+ data++;
+ break;
+ } else {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
} else {
- ret = gif_error_from_lzw(res);
+ len -= block_size;
+ data += block_size;
}
- break;
}
- }
- if (pixels == 0) {
- ret = GIF_OK;
+ gif->buffer_position = data - gif->gif_data;
+ gif->frame_count = frame_idx + 1;
+ gif->frames[frame_idx].display = true;
+
+ /* Check if we've finished */
+ if (len < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ } else {
+ if (data[0] == GIF_TRAILER) {
+ return GIF_OK;
+ }
+ }
+
+ return GIF_WORKING;
}
return ret;
}
-static inline gif_result gif__decode(
+static struct gif_frame *gif__get_frame(
struct gif_animation *gif,
- struct gif_frame *frame,
- uint8_t minimum_code_size,
- uint32_t *restrict frame_data)
+ uint32_t frame_idx)
{
- gif_result ret;
- uint32_t offset_x = frame->redraw_x;
- uint32_t offset_y = frame->redraw_y;
- uint32_t width = frame->redraw_width;
- uint32_t height = frame->redraw_height;
- uint32_t interlace = frame->flags & GIF_INTERLACE_MASK;
- uint32_t transparency_index = frame->transparency_index;
- uint32_t *restrict colour_table = gif->colour_table;
+ struct gif_frame *frame;
- if (interlace == false && width == gif->width && offset_x == 0) {
- ret = gif__decode_simple(gif, height, offset_y,
- minimum_code_size, transparency_index,
- frame_data, colour_table);
+ if (gif->frame_holders > frame_idx) {
+ frame = &gif->frames[frame_idx];
} else {
- ret = gif__decode_complex(gif, width, height,
- offset_x, offset_y, interlace,
- minimum_code_size, transparency_index,
- frame_data, colour_table);
+ /* Allocate more memory */
+ size_t count = frame_idx + 1;
+ struct gif_frame *temp;
+
+ temp = realloc(gif->frames, count * sizeof(*frame));
+ if (temp == NULL) {
+ return NULL;
+ }
+ gif->frames = temp;
+ gif->frame_holders = count;
+
+ frame = &gif->frames[frame_idx];
+
+ frame->transparency = false;
+ frame->transparency_index = GIF_NO_TRANSPARENCY;
+ frame->frame_pointer = gif->buffer_position;
+ frame->redraw_required = false;
+ frame->disposal_method = 0;
+ frame->frame_delay = 100;
+ frame->display = false;
+ frame->virgin = true;
}
- return ret;
+ return frame;
}
/**
- * Clear a gif frame.
+ * Attempts to initialise the next frame
*
- * \param[in] gif The gif object we're decoding.
- * \param[in] frame The frame to clear.
- * \param[in] bitmap The bitmap to clear the frame in.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif_clear_frame(
+ * \param[in] gif The animation context
+ * \param[in] frame_idx The frame number to decode.
+ * \return error code
+ * - GIF_INSUFFICIENT_DATA for insufficient data to do anything
+ * - GIF_FRAME_DATA_ERROR for GIF frame data error
+ * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
+ * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
+ * - GIF_DATA_ERROR for GIF error (invalid frame header)
+ * - GIF_OK for successful decoding
+ * - GIF_WORKING for successful decoding if more frames are expected
+*/
+static gif_result gif_initialise_frame(
struct gif_animation *gif,
- struct gif_frame *frame,
- uint32_t *bitmap)
+ uint32_t frame_idx)
{
- uint32_t width;
- uint32_t height;
- uint32_t offset_x;
- uint32_t offset_y;
+ gif_result ret;
+ struct gif_frame *frame;
+ uint8_t *gif_data, *gif_end;
+ int gif_bytes;
- assert(frame->disposal_method == GIF_FRAME_CLEAR);
+ /* Get our buffer position etc. */
+ gif_data = (uint8_t *)(gif->gif_data + gif->buffer_position);
+ gif_end = (uint8_t *)(gif->gif_data + gif->buffer_size);
+ gif_bytes = (gif_end - gif_data);
- /* Ensure this frame is supposed to be decoded */
- if (frame->display == false) {
+ /* Check if we've finished */
+ if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) {
return GIF_OK;
}
- offset_x = frame->redraw_x;
- offset_y = frame->redraw_y;
- width = frame->redraw_width;
- height = frame->redraw_height;
+ /* We could theoretically get some junk data that gives us millions of
+ * frames, so we ensure that we don't have a silly number
+ */
+ if (frame_idx > 4096) {
+ return GIF_FRAME_DATA_ERROR;
+ }
- /* Clear our frame */
- for (uint32_t y = 0; y < height; y++) {
- uint32_t *scanline;
- scanline = bitmap + offset_x + ((offset_y + y) * gif->width);
- if (frame->transparency) {
- memset(scanline, GIF_TRANSPARENT_COLOUR, width * 4);
- } else {
- for (uint32_t x = 0; x < width; x++) {
- scanline[x] = gif->bg_colour;
- }
- }
+ frame = gif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
}
- return GIF_OK;
-}
+ /* Initialise any extensions */
+ gif->buffer_position = gif_data - gif->gif_data;
+ ret = gif__parse_frame_extensions(gif, &gif->frames[frame_idx], true);
+ if (ret != GIF_OK) {
+ return ret;
+ }
-/**
- * Wipe bitmap to transparent.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] bitmap The bitmap to wipe.
- */
-static inline void gif__wipe_bitmap(
- const struct gif_animation *gif,
- uint32_t *bitmap)
-{
- memset((char*)bitmap, GIF_TRANSPARENT_COLOUR,
- gif->width * gif->height * sizeof(*bitmap));
+ ret = gif__parse_image_descriptor(gif, &gif->frames[frame_idx], true);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+
+ ret = gif__parse_colour_table(gif, &gif->frames[frame_idx], false);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+
+ ret = gif__parse_image_data(gif, &gif->frames[frame_idx], false);
+ if (ret != GIF_OK) {
+ return ret;
+ }
+
+ return GIF_OK;
}
/**
@@ -893,15 +1035,13 @@ static inline void gif__wipe_bitmap(
* \param frame The frame number to decode.
* \param clear_image flag for image data being cleared instead of plotted.
*/
-static gif_result
-gif_internal_decode_frame(gif_animation *gif,
- uint32_t frame_idx)
+static gif_result gif_internal_decode_frame(
+ struct gif_animation *gif,
+ uint32_t frame_idx)
{
gif_result ret;
+ uint8_t *gif_data;
struct gif_frame *frame;
- uint8_t *gif_data, *gif_end;
- int gif_bytes;
- uint32_t *frame_data = 0; // Set to 0 for no warnings
uint32_t save_buffer_position;
/* Ensure the frame is in range to decode */
@@ -914,7 +1054,10 @@ gif_internal_decode_frame(gif_animation *gif,
return GIF_OK;
}
- frame = &gif->frames[frame_idx];
+ frame = gif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
/* Ensure this frame is supposed to be decoded */
if (frame->display == false) {
@@ -923,8 +1066,6 @@ gif_internal_decode_frame(gif_animation *gif,
/* Get the start of our frame data and the end of the GIF data */
gif_data = gif->gif_data + frame->frame_pointer;
- gif_end = gif->gif_data + gif->buffer_size;
- gif_bytes = (gif_end - gif_data);
/* Save the buffer position */
save_buffer_position = gif->buffer_position;
@@ -943,108 +1084,16 @@ gif_internal_decode_frame(gif_animation *gif,
ret = gif__parse_colour_table(gif, frame, true);
if (ret != GIF_OK) {
- return ret;
- }
- gif_data = gif->gif_data + gif->buffer_position;
- gif_bytes = (gif_end - gif_data);
-
- /* Ensure sufficient data remains */
- if (gif_bytes < 1) {
- ret = GIF_INSUFFICIENT_FRAME_DATA;
- goto gif_decode_frame_exit;
- }
-
- /* check for an end marker */
- if (gif_data[0] == GIF_TRAILER) {
- ret = GIF_OK;
- goto gif_decode_frame_exit;
- }
-
- /* Make sure we have a buffer to decode to. */
- if (gif_initialise_sprite(gif, gif->width, gif->height)) {
- ret = GIF_INSUFFICIENT_MEMORY;
- goto gif_decode_frame_exit;
- }
-
- /* Get the frame data */
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
- ret = GIF_INSUFFICIENT_MEMORY;
- goto gif_decode_frame_exit;
- }
-
- /* Ensure we have enough data for a 1-byte LZW code size +
- * 1-byte gif trailer
- */
- if (gif_bytes < 2) {
- ret = GIF_INSUFFICIENT_FRAME_DATA;
goto gif_decode_frame_exit;
}
- /* If we only have a 1-byte LZW code size + 1-byte gif trailer,
- * we're finished
- */
- if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) {
- ret = GIF_OK;
+ ret = gif__parse_image_data(gif, frame, true);
+ if (ret != GIF_OK) {
goto gif_decode_frame_exit;
}
- /* Handle any bitmap clearing/restoration required before decoding this
- * frame. */
- if (frame_idx == 0 || gif->decoded_frame == GIF_INVALID_FRAME) {
- gif__wipe_bitmap(gif, frame_data);
-
- } else {
- struct gif_frame *prev = &gif->frames[frame_idx - 1];
-
- if (prev->disposal_method == GIF_FRAME_CLEAR) {
- ret = gif_clear_frame(gif, prev, frame_data);
- if (ret != GIF_OK) {
- goto gif_decode_frame_exit;
- }
- } else if (prev->disposal_method == GIF_FRAME_RESTORE) {
- /*
- * If the previous frame's disposal method requires we
- * restore the previous image, restore our saved image.
- */
- ret = gif__recover_frame(gif);
- if (ret != GIF_OK) {
- gif__wipe_bitmap(gif, frame_data);
- }
- }
- }
-
- if (frame->disposal_method == GIF_FRAME_RESTORE) {
- /* Store the previous frame for later restoration */
- gif__record_frame(gif);
- }
-
- gif->decoded_frame = frame_idx;
- gif->buffer_position = (gif_data - gif->gif_data) + 1;
-
- ret = gif__decode(gif, frame, gif_data[0], frame_data);
-
gif_decode_frame_exit:
- if (gif->bitmap_callbacks.bitmap_modified) {
- gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
- }
-
- /* Check if we should test for optimisation */
- if (frame->virgin) {
- if (gif->bitmap_callbacks.bitmap_test_opaque) {
- frame->opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image);
- } else {
- frame->opaque = false;
- }
- frame->virgin = false;
- }
-
- if (gif->bitmap_callbacks.bitmap_set_opaque) {
- gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, frame->opaque);
- }
-
/* Restore the buffer position */
gif->buffer_position = save_buffer_position;
@@ -1098,6 +1147,7 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned
char *data)
*/
gif->frame_image = NULL;
gif->frames = NULL;
+ gif->frame_holders = 0;
gif->local_colour_table = NULL;
gif->global_colour_table = NULL;
@@ -1192,13 +1242,6 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned
char *data)
}
}
- /* Initialise enough workspace for a frame */
- if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) {
- gif_finalise(gif);
- return GIF_INSUFFICIENT_MEMORY;
- }
- gif->frame_holders = 1;
-
/* Remember we've done this now */
gif->buffer_position = gif_data - gif->gif_data;
}
--
NetSurf GIF Decoder