summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libnsgif.c2168
-rw-r--r--src/lzw.c449
-rw-r--r--src/lzw.h56
-rw-r--r--test/decode_gif.c4
4 files changed, 1489 insertions, 1188 deletions
diff --git a/src/libnsgif.c b/src/libnsgif.c
index 95bbbfb..5fa9cb6 100644
--- a/src/libnsgif.c
+++ b/src/libnsgif.c
@@ -42,6 +42,9 @@
/** Transparent colour */
#define GIF_TRANSPARENT_COLOUR 0x00
+/** No transparency */
+#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu)
+
/* GIF Flags */
#define GIF_FRAME_COMBINE 1
#define GIF_FRAME_CLEAR 2
@@ -76,38 +79,21 @@
*/
static gif_result
gif_initialise_sprite(gif_animation *gif,
- unsigned int width,
- unsigned int height)
+ unsigned int width,
+ unsigned int height)
{
- unsigned int max_width;
- unsigned int max_height;
- struct bitmap *buffer;
-
- /* Check if we've changed */
- if ((width <= gif->width) && (height <= gif->height)) {
- return GIF_OK;
- }
-
- /* Get our maximum values */
- max_width = (width > gif->width) ? width : gif->width;
- max_height = (height > gif->height) ? height : gif->height;
-
- /* Allocate some more memory */
- assert(gif->bitmap_callbacks.bitmap_create);
- buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height);
- if (buffer == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- assert(gif->bitmap_callbacks.bitmap_destroy);
- gif->bitmap_callbacks.bitmap_destroy(gif->frame_image);
- gif->frame_image = buffer;
- gif->width = max_width;
- gif->height = max_height;
-
- /* Invalidate our currently decoded image */
- gif->decoded_frame = GIF_INVALID_FRAME;
- return GIF_OK;
+ /* Already allocated? */
+ if (gif->frame_image) {
+ return GIF_OK;
+ }
+
+ assert(gif->bitmap_callbacks.bitmap_create);
+ gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height);
+ if (gif->frame_image == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ return GIF_OK;
}
@@ -122,117 +108,117 @@ gif_initialise_sprite(gif_animation *gif,
static gif_result
gif_initialise_frame_extensions(gif_animation *gif, const int frame)
{
- unsigned char *gif_data, *gif_end;
- int gif_bytes;
- unsigned int block_size;
-
- /* Get our buffer position etc. */
- gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
- gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size);
-
- /* Initialise the extensions */
- while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
- ++gif_data;
- if ((gif_bytes = (gif_end - gif_data)) < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- /* Switch on extension label */
- switch (gif_data[0]) {
- case GIF_EXTENSION_GRAPHIC_CONTROL:
- /* 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 (gif_bytes < 6) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8);
- if (gif_data[2] & GIF_TRANSPARENCY_MASK) {
- gif->frames[frame].transparency = true;
- gif->frames[frame].transparency_index = gif_data[5];
- }
- gif->frames[frame].disposal_method = ((gif_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 (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) {
- gif->frames[frame].disposal_method = GIF_FRAME_RESTORE;
- }
- gif_data += (2 + gif_data[1]);
- break;
-
- case GIF_EXTENSION_APPLICATION:
- /* 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 (gif_bytes < 17) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- if ((gif_data[1] == 0x0b) &&
- (strncmp((const char *) gif_data + 2,
- "NETSCAPE2.0", 11) == 0) &&
- (gif_data[13] == 0x03) &&
- (gif_data[14] == 0x01)) {
- gif->loop_count = gif_data[15] | (gif_data[16] << 8);
- }
- gif_data += (2 + gif_data[1]);
- break;
-
- case GIF_EXTENSION_COMMENT:
- /* Move the pointer to the first data sub-block Skip 1
- * byte for the extension label
- */
- ++gif_data;
- break;
-
- default:
- /* 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]);
- }
-
- /* Repeatedly skip blocks until we get a zero block or run out
- * of data This data is ignored by this gif decoder
- */
- gif_bytes = (gif_end - gif_data);
- block_size = 0;
- while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
- block_size = gif_data[0] + 1;
- if ((gif_bytes -= block_size) < 0) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- gif_data += block_size;
- }
- ++gif_data;
- }
-
- /* Set buffer position and return */
- gif->buffer_position = (gif_data - gif->gif_data);
- return GIF_OK;
+ unsigned char *gif_data, *gif_end;
+ int gif_bytes;
+ unsigned int block_size;
+
+ /* Get our buffer position etc. */
+ gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
+ gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size);
+
+ /* Initialise the extensions */
+ while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
+ ++gif_data;
+ if ((gif_bytes = (gif_end - gif_data)) < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ /* Switch on extension label */
+ switch (gif_data[0]) {
+ case GIF_EXTENSION_GRAPHIC_CONTROL:
+ /* 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 (gif_bytes < 6) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8);
+ if (gif_data[2] & GIF_TRANSPARENCY_MASK) {
+ gif->frames[frame].transparency = true;
+ gif->frames[frame].transparency_index = gif_data[5];
+ }
+ gif->frames[frame].disposal_method = ((gif_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 (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) {
+ gif->frames[frame].disposal_method = GIF_FRAME_RESTORE;
+ }
+ gif_data += (2 + gif_data[1]);
+ break;
+
+ case GIF_EXTENSION_APPLICATION:
+ /* 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 (gif_bytes < 17) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ if ((gif_data[1] == 0x0b) &&
+ (strncmp((const char *) gif_data + 2,
+ "NETSCAPE2.0", 11) == 0) &&
+ (gif_data[13] == 0x03) &&
+ (gif_data[14] == 0x01)) {
+ gif->loop_count = gif_data[15] | (gif_data[16] << 8);
+ }
+ gif_data += (2 + gif_data[1]);
+ break;
+
+ case GIF_EXTENSION_COMMENT:
+ /* Move the pointer to the first data sub-block Skip 1
+ * byte for the extension label
+ */
+ ++gif_data;
+ break;
+
+ default:
+ /* 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]);
+ }
+
+ /* Repeatedly skip blocks until we get a zero block or run out
+ * of data This data is ignored by this gif decoder
+ */
+ gif_bytes = (gif_end - gif_data);
+ block_size = 0;
+ while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
+ block_size = gif_data[0] + 1;
+ if ((gif_bytes -= block_size) < 0) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ gif_data += block_size;
+ }
+ ++gif_data;
+ }
+
+ /* Set buffer position and return */
+ gif->buffer_position = (gif_data - gif->gif_data);
+ return GIF_OK;
}
@@ -251,229 +237,230 @@ gif_initialise_frame_extensions(gif_animation *gif, const int frame)
*/
static gif_result gif_initialise_frame(gif_animation *gif)
{
- int frame;
- gif_frame *temp_buf;
-
- unsigned char *gif_data, *gif_end;
- int gif_bytes;
- unsigned int flags = 0;
- unsigned int width, height, offset_x, offset_y;
- unsigned int block_size, colour_table_size;
- bool first_image = true;
- gif_result return_value;
-
- /* Get the frame to decode and our data position */
- frame = gif->frame_count;
-
- /* Get our buffer position etc. */
- gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
- gif_end = (unsigned char *)(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 > 4096) {
- return GIF_FRAME_DATA_ERROR;
- }
-
- /* Get some memory to store our pointers in etc. */
- if ((int)gif->frame_holders <= frame) {
- /* Allocate more memory */
- temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame));
- if (temp_buf == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
- gif->frames = temp_buf;
- gif->frame_holders = frame + 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].frame_pointer = gif->buffer_position;
- gif->frames[frame].display = false;
- gif->frames[frame].virgin = true;
- gif->frames[frame].disposal_method = 0;
- gif->frames[frame].transparency = false;
- gif->frames[frame].frame_delay = 100;
- gif->frames[frame].redraw_required = false;
-
- /* Invalidate any previous decoding we have of this frame */
- if (gif->decoded_frame == frame) {
- gif->decoded_frame = GIF_INVALID_FRAME;
- }
-
- /* 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;
- return_value = gif_initialise_frame_extensions(gif, frame);
- if (return_value != GIF_OK) {
- return return_value;
- }
- gif_data = (gif->gif_data + gif->buffer_position);
- gif_bytes = (gif_end - gif_data);
-
- /* Check if we've finished */
- if ((gif_bytes = (gif_end - gif_data)) < 1) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- if (gif_data[0] == GIF_TRAILER) {
- gif->buffer_position = (gif_data - gif->gif_data);
- gif->frame_count = frame + 1;
- return GIF_OK;
- }
-
- /* If we're not done, there should be an image descriptor */
- if (gif_data[0] != GIF_IMAGE_SEPARATOR) {
- return GIF_FRAME_DATA_ERROR;
- }
-
- /* Do some simple boundary checking */
- if (gif_bytes < 10) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- offset_x = gif_data[1] | (gif_data[2] << 8);
- offset_y = gif_data[3] | (gif_data[4] << 8);
- width = gif_data[5] | (gif_data[6] << 8);
- height = gif_data[7] | (gif_data[8] << 8);
-
- /* Set up the redraw characteristics. We have to check for extending
- * the area due to multi-image frames.
- */
- if (!first_image) {
- if (gif->frames[frame].redraw_x > offset_x) {
- gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x);
- gif->frames[frame].redraw_x = offset_x;
- }
-
- if (gif->frames[frame].redraw_y > offset_y) {
- gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y);
- gif->frames[frame].redraw_y = offset_y;
- }
-
- if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) {
- gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x;
- }
-
- if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) {
- gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y;
- }
- } else {
- first_image = false;
- gif->frames[frame].redraw_x = offset_x;
- gif->frames[frame].redraw_y = offset_y;
- gif->frames[frame].redraw_width = width;
- gif->frames[frame].redraw_height = height;
- }
-
- /* if we are clearing the background then we need to redraw enough to
- * cover the previous frame too
- */
- gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) ||
- (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE));
-
- /* Boundary checking - shouldn't ever happen except with junk data */
- if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* Decode the flags */
- flags = gif_data[9];
- colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
-
- /* Move our data onwards and remember we've got a bit of this frame */
- gif_data += 10;
- gif_bytes = (gif_end - gif_data);
- gif->frame_count_partial = frame + 1;
-
- /* Skip the local colour table */
- if (flags & GIF_COLOUR_TABLE_MASK) {
- gif_data += 3 * colour_table_size;
- if ((gif_bytes = (gif_end - gif_data)) < 0) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- }
-
- /* 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 + 1;
- gif->frames[frame].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;
+ int frame;
+ gif_frame *temp_buf;
+
+ unsigned char *gif_data, *gif_end;
+ int gif_bytes;
+ unsigned int flags = 0;
+ unsigned int width, height, offset_x, offset_y;
+ unsigned int block_size, colour_table_size;
+ bool first_image = true;
+ gif_result return_value;
+
+ /* Get the frame to decode and our data position */
+ frame = gif->frame_count;
+
+ /* Get our buffer position etc. */
+ gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
+ gif_end = (unsigned char *)(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 > 4096) {
+ return GIF_FRAME_DATA_ERROR;
+ }
+
+ /* Get some memory to store our pointers in etc. */
+ if ((int)gif->frame_holders <= frame) {
+ /* Allocate more memory */
+ temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame));
+ if (temp_buf == NULL) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+ gif->frames = temp_buf;
+ gif->frame_holders = frame + 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].frame_pointer = gif->buffer_position;
+ gif->frames[frame].display = false;
+ gif->frames[frame].virgin = true;
+ gif->frames[frame].disposal_method = 0;
+ gif->frames[frame].transparency = false;
+ gif->frames[frame].frame_delay = 100;
+ gif->frames[frame].redraw_required = false;
+
+ /* Invalidate any previous decoding we have of this frame */
+ if (gif->decoded_frame == frame) {
+ gif->decoded_frame = GIF_INVALID_FRAME;
+ }
+
+ /* 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;
+ return_value = gif_initialise_frame_extensions(gif, frame);
+ if (return_value != GIF_OK) {
+ return return_value;
+ }
+ gif_data = (gif->gif_data + gif->buffer_position);
+ gif_bytes = (gif_end - gif_data);
+
+ /* Check if we've finished */
+ if ((gif_bytes = (gif_end - gif_data)) < 1) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ if (gif_data[0] == GIF_TRAILER) {
+ gif->buffer_position = (gif_data - gif->gif_data);
+ gif->frame_count = frame + 1;
+ return GIF_OK;
+ }
+
+ /* If we're not done, there should be an image descriptor */
+ if (gif_data[0] != GIF_IMAGE_SEPARATOR) {
+ return GIF_FRAME_DATA_ERROR;
+ }
+
+ /* Do some simple boundary checking */
+ if (gif_bytes < 10) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ offset_x = gif_data[1] | (gif_data[2] << 8);
+ offset_y = gif_data[3] | (gif_data[4] << 8);
+ width = gif_data[5] | (gif_data[6] << 8);
+ height = gif_data[7] | (gif_data[8] << 8);
+
+ /* Set up the redraw characteristics. We have to check for extending
+ * the area due to multi-image frames.
+ */
+ if (!first_image) {
+ if (gif->frames[frame].redraw_x > offset_x) {
+ gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x);
+ gif->frames[frame].redraw_x = offset_x;
+ }
+
+ if (gif->frames[frame].redraw_y > offset_y) {
+ gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y);
+ gif->frames[frame].redraw_y = offset_y;
+ }
+
+ if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) {
+ gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x;
+ }
+
+ if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) {
+ gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y;
+ }
+ } else {
+ first_image = false;
+ gif->frames[frame].redraw_x = offset_x;
+ gif->frames[frame].redraw_y = offset_y;
+ gif->frames[frame].redraw_width = width;
+ gif->frames[frame].redraw_height = height;
+ }
+
+ /* if we are clearing the background then we need to redraw enough to
+ * cover the previous frame too
+ */
+ gif->frames[frame].redraw_required =
+ ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) ||
+ (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE));
+
+ /* Frame size may have grown.
+ */
+ gif->width = (offset_x + width > gif->width) ?
+ offset_x + width : gif->width;
+ gif->height = (offset_y + height > gif->height) ?
+ offset_y + height : gif->height;
+
+ /* Decode the flags */
+ flags = gif_data[9];
+ colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
+
+ /* Move our data onwards and remember we've got a bit of this frame */
+ gif_data += 10;
+ gif_bytes = (gif_end - gif_data);
+ gif->frame_count_partial = frame + 1;
+
+ /* Skip the local colour table */
+ if (flags & GIF_COLOUR_TABLE_MASK) {
+ gif_data += 3 * colour_table_size;
+ if ((gif_bytes = (gif_end - gif_data)) < 0) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ }
+
+ /* 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 + 1;
+ gif->frames[frame].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;
}
-
-
/**
* Skips the frame's extensions (which have been previously initialised)
*
@@ -483,159 +470,313 @@ static gif_result gif_initialise_frame(gif_animation *gif)
*/
static gif_result gif_skip_frame_extensions(gif_animation *gif)
{
- unsigned char *gif_data, *gif_end;
- int gif_bytes;
- unsigned int block_size;
-
- /* Get our buffer position etc. */
- gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
- gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size);
- gif_bytes = (gif_end - gif_data);
-
- /* Skip the extensions */
- while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
- ++gif_data;
- if (gif_data >= gif_end) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- /* Switch on extension label */
- switch(gif_data[0]) {
- case GIF_EXTENSION_COMMENT:
- /* Move the pointer to the first data sub-block
- * 1 byte for the extension label
- */
- ++gif_data;
- break;
-
- default:
- /* Move the pointer to the first data sub-block 2 bytes
- * for the extension label and size fields Skip the
- * extension size itself
- */
- if (gif_data + 1 >= gif_end) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- gif_data += (2 + gif_data[1]);
- }
-
- /* Repeatedly skip blocks until we get a zero block or run out
- * of data This data is ignored by this gif decoder
- */
- gif_bytes = (gif_end - gif_data);
- block_size = 0;
- while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
- block_size = gif_data[0] + 1;
- if ((gif_bytes -= block_size) < 0) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
- gif_data += block_size;
- }
- ++gif_data;
- }
-
- /* Set buffer position and return */
- gif->buffer_position = (gif_data - gif->gif_data);
- return GIF_OK;
+ unsigned char *gif_data, *gif_end;
+ int gif_bytes;
+ unsigned int block_size;
+
+ /* Get our buffer position etc. */
+ gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position);
+ gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size);
+ gif_bytes = (gif_end - gif_data);
+
+ /* Skip the extensions */
+ while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) {
+ ++gif_data;
+ if (gif_data >= gif_end) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ /* Switch on extension label */
+ switch(gif_data[0]) {
+ case GIF_EXTENSION_COMMENT:
+ /* Move the pointer to the first data sub-block
+ * 1 byte for the extension label
+ */
+ ++gif_data;
+ break;
+
+ default:
+ /* Move the pointer to the first data sub-block 2 bytes
+ * for the extension label and size fields Skip the
+ * extension size itself
+ */
+ if (gif_data + 1 >= gif_end) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ gif_data += (2 + gif_data[1]);
+ }
+
+ /* Repeatedly skip blocks until we get a zero block or run out
+ * of data This data is ignored by this gif decoder
+ */
+ gif_bytes = (gif_end - gif_data);
+ block_size = 0;
+ while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) {
+ block_size = gif_data[0] + 1;
+ if ((gif_bytes -= block_size) < 0) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+ gif_data += block_size;
+ }
+ ++gif_data;
+ }
+
+ /* Set buffer position and return */
+ gif->buffer_position = (gif_data - gif->gif_data);
+ return GIF_OK;
}
static unsigned int 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;
- }
- y -= ((height + 1) >> 2);
- return (y << 1) + 1;
+ 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;
}
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,
- };
- return g_res[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,
+ };
+ return g_res[l_res];
}
static void gif__record_previous_frame(gif_animation *gif)
{
- bool need_alloc = gif->prev_frame == NULL;
- const uint32_t *frame_data;
- uint32_t *prev_frame;
-
- if (gif->decoded_frame == GIF_INVALID_FRAME ||
- gif->decoded_frame == gif->prev_index) {
- /* No frame to copy, or already have this frame recorded. */
- return;
- }
-
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
- return;
- }
-
- if (gif->prev_frame != NULL &&
- gif->width * gif->height < gif->prev_width * gif->prev_height) {
- need_alloc = true;
- }
-
- if (need_alloc) {
- prev_frame = realloc(gif->prev_frame,
- gif->width * gif->height * 4);
- if (prev_frame == NULL) {
- return;
- }
- } else {
- prev_frame = gif->prev_frame;
- }
-
- memcpy(prev_frame, frame_data, gif->width * gif->height * 4);
-
- gif->prev_frame = prev_frame;
- gif->prev_width = gif->width;
- gif->prev_height = gif->height;
- gif->prev_index = gif->decoded_frame;
+ bool need_alloc = gif->prev_frame == NULL;
+ const uint32_t *frame_data;
+ uint32_t *prev_frame;
+
+ if (gif->decoded_frame == GIF_INVALID_FRAME ||
+ gif->decoded_frame == gif->prev_index) {
+ /* No frame to copy, or already have this frame recorded. */
+ return;
+ }
+
+ assert(gif->bitmap_callbacks.bitmap_get_buffer);
+ frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
+ if (!frame_data) {
+ return;
+ }
+
+ if (gif->prev_frame != NULL &&
+ gif->width * gif->height > gif->prev_width * gif->prev_height) {
+ need_alloc = true;
+ }
+
+ if (need_alloc) {
+ prev_frame = realloc(gif->prev_frame,
+ gif->width * gif->height * 4);
+ if (prev_frame == NULL) {
+ return;
+ }
+ } else {
+ prev_frame = gif->prev_frame;
+ }
+
+ memcpy(prev_frame, frame_data, gif->width * gif->height * 4);
+
+ gif->prev_frame = prev_frame;
+ gif->prev_width = gif->width;
+ gif->prev_height = gif->height;
+ gif->prev_index = gif->decoded_frame;
}
static gif_result gif__recover_previous_frame(const gif_animation *gif)
{
- const uint32_t *prev_frame = gif->prev_frame;
- unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height;
- unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width;
- uint32_t *frame_data;
+ 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;
+ }
- 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;
+ }
- assert(gif->bitmap_callbacks.bitmap_get_buffer);
- frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
- if (!frame_data) {
- return GIF_INSUFFICIENT_MEMORY;
- }
+ for (unsigned y = 0; y < height; y++) {
+ memcpy(frame_data, prev_frame, width * 4);
+
+ frame_data += gif->width;
+ prev_frame += gif->prev_width;
+ }
+
+ return GIF_OK;
+}
- for (unsigned y = 0; y < height; y++) {
- memcpy(frame_data, prev_frame, width * 4);
+static gif_result
+gif__decode_complex(gif_animation *gif,
+ unsigned int frame,
+ unsigned int width,
+ unsigned int height,
+ unsigned int offset_x,
+ unsigned int offset_y,
+ unsigned int interlace,
+ uint8_t minimum_code_size,
+ unsigned int *restrict frame_data,
+ unsigned int *restrict colour_table)
+{
+ unsigned int transparency_index;
+ uint32_t available = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
+
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
+ gif->buffer_size, gif->buffer_position,
+ minimum_code_size);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
+ }
+
+ transparency_index = gif->frames[frame].transparency ?
+ gif->frames[frame].transparency_index :
+ GIF_NO_TRANSPARENCY;
+
+ for (unsigned int y = 0; y < height; y++) {
+ unsigned int x;
+ unsigned int decode_y;
+ unsigned int *frame_scanline;
+
+ 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 = 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_continuous(gif->lzw_ctx,
+ &uncompressed, &available);
+ }
+
+ row_available = x < available ? x : available;
+ x -= row_available;
+ available -= row_available;
+ while (row_available-- > 0) {
+ register unsigned int colour;
+ colour = *uncompressed++;
+ if (colour != transparency_index) {
+ *frame_scanline = colour_table[colour];
+ }
+ frame_scanline++;
+ }
+ }
+ }
+ return ret;
+}
- frame_data += gif->width;
- prev_frame += gif->prev_width;
- }
+static gif_result
+gif__decode_simple(gif_animation *gif,
+ unsigned int frame,
+ unsigned int height,
+ unsigned int offset_y,
+ uint8_t minimum_code_size,
+ unsigned int *restrict frame_data,
+ unsigned int *restrict colour_table)
+{
+ unsigned int transparency_index;
+ uint32_t pixels = gif->width * height;
+ uint32_t written = 0;
+ gif_result ret = GIF_OK;
+ lzw_result res;
+
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
+ gif->buffer_size, gif->buffer_position,
+ minimum_code_size);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
+ }
+
+ transparency_index = gif->frames[frame].transparency ?
+ gif->frames[frame].transparency_index :
+ GIF_NO_TRANSPARENCY;
+
+ frame_data += (offset_y * gif->width);
+
+ while (pixels > 0) {
+ res = lzw_decode_map_continuous(gif->lzw_ctx,
+ transparency_index, colour_table,
+ 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;
+ }
+ }
+
+ if (pixels == 0) {
+ ret = GIF_OK;
+ }
+
+ return ret;
+}
- return GIF_OK;
+static inline gif_result
+gif__decode(gif_animation *gif,
+ unsigned int frame,
+ unsigned int width,
+ unsigned int height,
+ unsigned int offset_x,
+ unsigned int offset_y,
+ unsigned int interlace,
+ uint8_t minimum_code_size,
+ unsigned int *restrict frame_data,
+ unsigned int *restrict colour_table)
+{
+ gif_result ret;
+
+ if (interlace == false && width == gif->width && offset_x == 0) {
+ ret = gif__decode_simple(gif, frame, height, offset_y,
+ minimum_code_size, frame_data, colour_table);
+ } else {
+ ret = gif__decode_complex(gif, frame, width, height,
+ offset_x, offset_y, interlace,
+ minimum_code_size, frame_data, colour_table);
+ }
+
+ return ret;
}
/**
@@ -647,582 +788,527 @@ static gif_result gif__recover_previous_frame(const gif_animation *gif)
*/
static gif_result
gif_internal_decode_frame(gif_animation *gif,
- unsigned int frame,
- bool clear_image)
+ unsigned int frame,
+ bool clear_image)
{
- gif_result err;
- unsigned int index = 0;
- unsigned char *gif_data, *gif_end;
- int gif_bytes;
- unsigned int width, height, offset_x, offset_y;
- unsigned int flags, colour_table_size, interlace;
- unsigned int *colour_table;
- unsigned int *frame_data = 0; // Set to 0 for no warnings
- unsigned int *frame_scanline;
- unsigned int save_buffer_position;
- unsigned int return_value = 0;
- unsigned int x, y, decode_y, burst_bytes;
- register unsigned char colour;
-
- /* Ensure this frame is supposed to be decoded */
- if (gif->frames[frame].display == false) {
- return GIF_OK;
- }
-
- /* Ensure the frame is in range to decode */
- if (frame > gif->frame_count_partial) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- /* done if frame is already decoded */
- if ((!clear_image) &&
- ((int)frame == gif->decoded_frame)) {
- return GIF_OK;
- }
-
- if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) {
- /* Store the previous frame for later restoration */
- gif__record_previous_frame(gif);
- }
-
- /* Get the start of our frame data and the end of the GIF data */
- gif_data = gif->gif_data + gif->frames[frame].frame_pointer;
- gif_end = gif->gif_data + gif->buffer_size;
- gif_bytes = (gif_end - gif_data);
-
- /*
- * Ensure there is a minimal amount of data to proceed. The shortest
- * block of data is a 10-byte image descriptor + 1-byte gif trailer
- */
- if (gif_bytes < 12) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- /* Save the buffer position */
- save_buffer_position = gif->buffer_position;
- gif->buffer_position = gif_data - gif->gif_data;
-
- /* Skip any extensions because they have allready been processed */
- if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) {
- goto gif_decode_frame_exit;
- }
- gif_data = (gif->gif_data + gif->buffer_position);
- gif_bytes = (gif_end - gif_data);
-
- /* Ensure we have enough data for the 10-byte image descriptor + 1-byte
- * gif trailer
- */
- if (gif_bytes < 12) {
- return_value = GIF_INSUFFICIENT_FRAME_DATA;
- goto gif_decode_frame_exit;
- }
-
- /* 10-byte Image Descriptor 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
- */
- if (gif_data[0] != GIF_IMAGE_SEPARATOR) {
- return_value = GIF_DATA_ERROR;
- goto gif_decode_frame_exit;
- }
- offset_x = gif_data[1] | (gif_data[2] << 8);
- offset_y = gif_data[3] | (gif_data[4] << 8);
- width = gif_data[5] | (gif_data[6] << 8);
- height = gif_data[7] | (gif_data[8] << 8);
-
- /* Boundary checking - shouldn't ever happen except unless the data has
- * been modified since initialisation.
- */
- if ((offset_x + width > gif->width) ||
- (offset_y + height > gif->height)) {
- return_value = GIF_DATA_ERROR;
- goto gif_decode_frame_exit;
- }
-
- /* Decode the flags */
- flags = gif_data[9];
- colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
- interlace = flags & GIF_INTERLACE_MASK;
-
- /* Advance data pointer to next block either colour table or image
- * data.
- */
- gif_data += 10;
- gif_bytes = (gif_end - gif_data);
-
- /* Set up the colour table */
- if (flags & GIF_COLOUR_TABLE_MASK) {
- if (gif_bytes < (int)(3 * colour_table_size)) {
- return_value = GIF_INSUFFICIENT_FRAME_DATA;
- goto gif_decode_frame_exit;
- }
- colour_table = gif->local_colour_table;
- if (!clear_image) {
- for (index = 0; index < colour_table_size; index++) {
- /* 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.
- */
- unsigned char *entry =
- (unsigned char *) &colour_table[index];
-
- entry[0] = gif_data[0]; /* r */
- entry[1] = gif_data[1]; /* g */
- entry[2] = gif_data[2]; /* b */
- entry[3] = 0xff; /* a */
-
- gif_data += 3;
- }
- } else {
- gif_data += 3 * colour_table_size;
- }
- gif_bytes = (gif_end - gif_data);
- } else {
- colour_table = gif->global_colour_table;
- }
-
- /* Ensure sufficient data remains */
- if (gif_bytes < 1) {
- return_value = GIF_INSUFFICIENT_FRAME_DATA;
- goto gif_decode_frame_exit;
- }
-
- /* check for an end marker */
- if (gif_data[0] == GIF_TRAILER) {
- return_value = GIF_OK;
- 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) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* If we are clearing the image we just clear, if not decode */
- if (!clear_image) {
- lzw_result res;
- const uint8_t *stack_base;
- const uint8_t *stack_pos;
-
- /* Ensure we have enough data for a 1-byte LZW code size +
- * 1-byte gif trailer
- */
- if (gif_bytes < 2) {
- return_value = 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)) {
- return_value = GIF_OK;
- goto gif_decode_frame_exit;
- }
-
- /* If the previous frame's disposal method requires we restore
- * the background colour or this is the first frame, clear
- * the frame data
- */
- if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) {
- memset((char*)frame_data,
- GIF_TRANSPARENT_COLOUR,
- gif->width * gif->height * sizeof(int));
- gif->decoded_frame = frame;
- /* The line below would fill the image with its
- * background color, but because GIFs support
- * transparency we likely wouldn't want to do that. */
- /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */
- } else if ((frame != 0) &&
- (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) {
- return_value = gif_internal_decode_frame(gif,
- (frame - 1),
- true);
- if (return_value != GIF_OK) {
- goto gif_decode_frame_exit;
- }
-
- } else if ((frame != 0) &&
- (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) {
- /*
- * If the previous frame's disposal method requires we
- * restore the previous image, restore our saved image.
- */
- err = gif__recover_previous_frame(gif);
- if (err != GIF_OK) {
- /* see notes above on transparency
- * vs. background color
- */
- memset((char*)frame_data,
- GIF_TRANSPARENT_COLOUR,
- gif->width * gif->height * sizeof(int));
- }
- }
- gif->decoded_frame = frame;
- gif->buffer_position = (gif_data - gif->gif_data) + 1;
-
- /* Initialise the LZW decoding */
- res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
- gif->buffer_size, gif->buffer_position,
- gif_data[0], &stack_base, &stack_pos);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
- }
-
- /* Decompress the data */
- for (y = 0; y < height; y++) {
- 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);
-
- /* Rather than decoding pixel by pixel, we try to burst
- * out streams of data to remove the need for end-of
- * data checks every pixel.
- */
- x = width;
- while (x > 0) {
- burst_bytes = (stack_pos - stack_base);
- if (burst_bytes > 0) {
- if (burst_bytes > x) {
- burst_bytes = x;
- }
- x -= burst_bytes;
- while (burst_bytes-- > 0) {
- colour = *--stack_pos;
- if (((gif->frames[frame].transparency) &&
- (colour != gif->frames[frame].transparency_index)) ||
- (!gif->frames[frame].transparency)) {
- *frame_scanline = colour_table[colour];
- }
- frame_scanline++;
- }
- } else {
- res = lzw_decode(gif->lzw_ctx, &stack_pos);
- if (res != LZW_OK) {
- /* Unexpected end of frame, try to recover */
- if (res == LZW_OK_EOD) {
- return_value = GIF_OK;
- } else {
- return_value = gif_error_from_lzw(res);
- }
- goto gif_decode_frame_exit;
- }
- }
- }
- }
- } else {
- /* Clear our frame */
- if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) {
- for (y = 0; y < height; y++) {
- frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width);
- if (gif->frames[frame].transparency) {
- memset(frame_scanline,
- GIF_TRANSPARENT_COLOUR,
- width * 4);
- } else {
- memset(frame_scanline,
- colour_table[gif->background_index],
- width * 4);
- }
- }
- }
- }
+ gif_result err;
+ unsigned int index = 0;
+ unsigned char *gif_data, *gif_end;
+ int gif_bytes;
+ unsigned int width, height, offset_x, offset_y;
+ unsigned int flags, colour_table_size, interlace;
+ unsigned int *colour_table;
+ unsigned int *frame_data = 0; // Set to 0 for no warnings
+ unsigned int save_buffer_position;
+ unsigned int return_value = 0;
+
+ /* Ensure this frame is supposed to be decoded */
+ if (gif->frames[frame].display == false) {
+ return GIF_OK;
+ }
+
+ /* Ensure the frame is in range to decode */
+ if (frame > gif->frame_count_partial) {
+ return GIF_INSUFFICIENT_DATA;
+ }
+
+ /* done if frame is already decoded */
+ if ((!clear_image) &&
+ ((int)frame == gif->decoded_frame)) {
+ return GIF_OK;
+ }
+
+ /* Get the start of our frame data and the end of the GIF data */
+ gif_data = gif->gif_data + gif->frames[frame].frame_pointer;
+ gif_end = gif->gif_data + gif->buffer_size;
+ gif_bytes = (gif_end - gif_data);
+
+ /*
+ * Ensure there is a minimal amount of data to proceed. The shortest
+ * block of data is a 10-byte image descriptor + 1-byte gif trailer
+ */
+ if (gif_bytes < 12) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ /* Save the buffer position */
+ save_buffer_position = gif->buffer_position;
+ gif->buffer_position = gif_data - gif->gif_data;
+
+ /* Skip any extensions because they have already been processed */
+ if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) {
+ goto gif_decode_frame_exit;
+ }
+ gif_data = (gif->gif_data + gif->buffer_position);
+ gif_bytes = (gif_end - gif_data);
+
+ /* Ensure we have enough data for the 10-byte image descriptor + 1-byte
+ * gif trailer
+ */
+ if (gif_bytes < 12) {
+ return_value = GIF_INSUFFICIENT_FRAME_DATA;
+ goto gif_decode_frame_exit;
+ }
+
+ /* 10-byte Image Descriptor 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
+ */
+ if (gif_data[0] != GIF_IMAGE_SEPARATOR) {
+ return_value = GIF_DATA_ERROR;
+ goto gif_decode_frame_exit;
+ }
+ offset_x = gif_data[1] | (gif_data[2] << 8);
+ offset_y = gif_data[3] | (gif_data[4] << 8);
+ width = gif_data[5] | (gif_data[6] << 8);
+ height = gif_data[7] | (gif_data[8] << 8);
+
+ /* Boundary checking - shouldn't ever happen except unless the data has
+ * been modified since initialisation.
+ */
+ if ((offset_x + width > gif->width) ||
+ (offset_y + height > gif->height)) {
+ return_value = GIF_DATA_ERROR;
+ goto gif_decode_frame_exit;
+ }
+
+ /* Make sure we have a buffer to decode to.
+ */
+ if (gif_initialise_sprite(gif, gif->width, gif->height)) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ /* Decode the flags */
+ flags = gif_data[9];
+ colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
+ interlace = flags & GIF_INTERLACE_MASK;
+
+ /* Advance data pointer to next block either colour table or image
+ * data.
+ */
+ gif_data += 10;
+ gif_bytes = (gif_end - gif_data);
+
+ /* Set up the colour table */
+ if (flags & GIF_COLOUR_TABLE_MASK) {
+ if (gif_bytes < (int)(3 * colour_table_size)) {
+ return_value = GIF_INSUFFICIENT_FRAME_DATA;
+ goto gif_decode_frame_exit;
+ }
+ colour_table = gif->local_colour_table;
+ if (!clear_image) {
+ for (index = 0; index < colour_table_size; index++) {
+ /* 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.
+ */
+ unsigned char *entry =
+ (unsigned char *) &colour_table[index];
+
+ entry[0] = gif_data[0]; /* r */
+ entry[1] = gif_data[1]; /* g */
+ entry[2] = gif_data[2]; /* b */
+ entry[3] = 0xff; /* a */
+
+ gif_data += 3;
+ }
+ } else {
+ gif_data += 3 * colour_table_size;
+ }
+ gif_bytes = (gif_end - gif_data);
+ } else {
+ colour_table = gif->global_colour_table;
+ }
+
+ /* Ensure sufficient data remains */
+ if (gif_bytes < 1) {
+ return_value = GIF_INSUFFICIENT_FRAME_DATA;
+ goto gif_decode_frame_exit;
+ }
+
+ /* check for an end marker */
+ if (gif_data[0] == GIF_TRAILER) {
+ return_value = GIF_OK;
+ 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) {
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ /* If we are clearing the image we just clear, if not decode */
+ if (!clear_image) {
+ /* Ensure we have enough data for a 1-byte LZW code size +
+ * 1-byte gif trailer
+ */
+ if (gif_bytes < 2) {
+ return_value = 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)) {
+ return_value = GIF_OK;
+ goto gif_decode_frame_exit;
+ }
+
+ /* If the previous frame's disposal method requires we restore
+ * the background colour or this is the first frame, clear
+ * the frame data
+ */
+ if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) {
+ memset((char*)frame_data,
+ GIF_TRANSPARENT_COLOUR,
+ gif->width * gif->height * sizeof(int));
+ gif->decoded_frame = frame;
+ /* The line below would fill the image with its
+ * background color, but because GIFs support
+ * transparency we likely wouldn't want to do that. */
+ /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */
+ } else if ((frame != 0) &&
+ (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) {
+ return_value = gif_internal_decode_frame(gif,
+ (frame - 1),
+ true);
+ if (return_value != GIF_OK) {
+ goto gif_decode_frame_exit;
+ }
+
+ } else if ((frame != 0) &&
+ (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) {
+ /*
+ * If the previous frame's disposal method requires we
+ * restore the previous image, restore our saved image.
+ */
+ err = gif__recover_previous_frame(gif);
+ if (err != GIF_OK) {
+ /* see notes above on transparency
+ * vs. background color
+ */
+ memset((char*)frame_data,
+ GIF_TRANSPARENT_COLOUR,
+ gif->width * gif->height * sizeof(int));
+ }
+ }
+
+ if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) {
+ /* Store the previous frame for later restoration */
+ gif__record_previous_frame(gif);
+ }
+
+ gif->decoded_frame = frame;
+ gif->buffer_position = (gif_data - gif->gif_data) + 1;
+
+ return_value = gif__decode(gif, frame, width, height,
+ offset_x, offset_y, interlace, gif_data[0],
+ frame_data, colour_table);
+ } else {
+ /* Clear our frame */
+ if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) {
+ unsigned int y;
+ for (y = 0; y < height; y++) {
+ unsigned int *frame_scanline;
+ frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width);
+ if (gif->frames[frame].transparency) {
+ memset(frame_scanline,
+ GIF_TRANSPARENT_COLOUR,
+ width * 4);
+ } else {
+ memset(frame_scanline,
+ colour_table[gif->background_index],
+ width * 4);
+ }
+ }
+ }
+ }
gif_decode_frame_exit:
- /* Check if we should test for optimisation */
- if (gif->frames[frame].virgin) {
- if (gif->bitmap_callbacks.bitmap_test_opaque) {
- gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image);
- } else {
- gif->frames[frame].opaque = false;
- }
- gif->frames[frame].virgin = false;
- }
+ /* Check if we should test for optimisation */
+ if (gif->frames[frame].virgin) {
+ if (gif->bitmap_callbacks.bitmap_test_opaque) {
+ gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image);
+ } else {
+ gif->frames[frame].opaque = false;
+ }
+ gif->frames[frame].virgin = false;
+ }
- if (gif->bitmap_callbacks.bitmap_set_opaque) {
- gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque);
- }
+ if (gif->bitmap_callbacks.bitmap_set_opaque) {
+ gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque);
+ }
- if (gif->bitmap_callbacks.bitmap_modified) {
- gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
- }
+ if (gif->bitmap_callbacks.bitmap_modified) {
+ gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
+ }
- /* Restore the buffer position */
- gif->buffer_position = save_buffer_position;
+ /* Restore the buffer position */
+ gif->buffer_position = save_buffer_position;
- return return_value;
+ return return_value;
}
/* exported function documented in libnsgif.h */
void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks)
{
- memset(gif, 0, sizeof(gif_animation));
- gif->bitmap_callbacks = *bitmap_callbacks;
- gif->decoded_frame = GIF_INVALID_FRAME;
- gif->prev_index = GIF_INVALID_FRAME;
+ memset(gif, 0, sizeof(gif_animation));
+ gif->bitmap_callbacks = *bitmap_callbacks;
+ gif->decoded_frame = GIF_INVALID_FRAME;
+ gif->prev_index = GIF_INVALID_FRAME;
}
/* exported function documented in libnsgif.h */
gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data)
{
- unsigned char *gif_data;
- unsigned int index;
- gif_result return_value;
-
- /* Initialize values */
- gif->buffer_size = size;
- gif->gif_data = data;
-
- if (gif->lzw_ctx == NULL) {
- lzw_result res = lzw_context_create(
- (struct lzw_ctx **)&gif->lzw_ctx);
- if (res != LZW_OK) {
- return gif_error_from_lzw(res);
- }
- }
-
- /* Check for sufficient data to be a GIF (6-byte header + 7-byte
- * logical screen descriptor)
- */
- if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- /* Get our current processing position */
- gif_data = gif->gif_data + gif->buffer_position;
-
- /* See if we should initialise the GIF */
- if (gif->buffer_position == 0) {
- /* We want everything to be NULL before we start so we've no
- * chance of freeing bad pointers (paranoia)
- */
- gif->frame_image = NULL;
- gif->frames = NULL;
- gif->local_colour_table = NULL;
- gif->global_colour_table = NULL;
-
- /* The caller may have been lazy and not reset any values */
- gif->frame_count = 0;
- gif->frame_count_partial = 0;
- gif->decoded_frame = GIF_INVALID_FRAME;
-
- /* 6-byte GIF file header is:
- *
- * +0 3CHARS Signature ('GIF')
- * +3 3CHARS Version ('87a' or '89a')
- */
- if (strncmp((const char *) gif_data, "GIF", 3) != 0) {
- return GIF_DATA_ERROR;
- }
- gif_data += 3;
-
- /* Ensure GIF reports version 87a or 89a */
- /*
- if ((strncmp(gif_data, "87a", 3) != 0) &&
- (strncmp(gif_data, "89a", 3) != 0))
- LOG(("Unknown GIF format - proceeding anyway"));
- */
- gif_data += 3;
-
- /* 7-byte Logical Screen Descriptor is:
- *
- * +0 SHORT Logical Screen Width
- * +2 SHORT Logical Screen Height
- * +4 CHAR __Packed Fields__
- * 1BIT Global Colour Table Flag
- * 3BITS Colour Resolution
- * 1BIT Sort Flag
- * 3BITS Size of Global Colour Table
- * +5 CHAR Background Colour Index
- * +6 CHAR Pixel Aspect Ratio
- */
- gif->width = gif_data[0] | (gif_data[1] << 8);
- gif->height = gif_data[2] | (gif_data[3] << 8);
- gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK);
- gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK));
- gif->background_index = gif_data[5];
- gif->aspect_ratio = gif_data[6];
- gif->loop_count = 1;
- gif_data += 7;
-
- /* Some broken GIFs report the size as the screen size they
- * were created in. As such, we detect for the common cases and
- * set the sizes as 0 if they are found which results in the
- * GIF being the maximum size of the frames.
- */
- if (((gif->width == 640) && (gif->height == 480)) ||
- ((gif->width == 640) && (gif->height == 512)) ||
- ((gif->width == 800) && (gif->height == 600)) ||
- ((gif->width == 1024) && (gif->height == 768)) ||
- ((gif->width == 1280) && (gif->height == 1024)) ||
- ((gif->width == 1600) && (gif->height == 1200)) ||
- ((gif->width == 0) || (gif->height == 0)) ||
- ((gif->width > 2048) || (gif->height > 2048))) {
- gif->width = 1;
- gif->height = 1;
- }
-
- /* Allocate some data irrespective of whether we've got any
- * colour tables. We always get the maximum size in case a GIF
- * is lying to us. It's far better to give the wrong colours
- * than to trample over some memory somewhere.
- */
- gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int));
- gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int));
- if ((gif->global_colour_table == NULL) ||
- (gif->local_colour_table == NULL)) {
- gif_finalise(gif);
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* Set the first colour to a value that will never occur in
- * reality so we know if we've processed it
- */
- gif->global_colour_table[0] = GIF_PROCESS_COLOURS;
-
- /* Check if the GIF has no frame data (13-byte header + 1-byte
- * termination block) Although generally useless, the GIF
- * specification does not expressly prohibit this
- */
- if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) {
- if (gif_data[0] == GIF_TRAILER) {
- return GIF_OK;
- } else {
- return GIF_INSUFFICIENT_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;
-
- /* Initialise the bitmap header */
- assert(gif->bitmap_callbacks.bitmap_create);
- gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height);
- if (gif->frame_image == NULL) {
- gif_finalise(gif);
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- /* Remember we've done this now */
- gif->buffer_position = gif_data - gif->gif_data;
- }
-
- /* Do the colour map if we haven't already. As the top byte is always
- * 0xff or 0x00 depending on the transparency we know if it's been
- * filled in.
- */
- if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) {
- /* Check for a global colour map signified by bit 7 */
- if (gif->global_colours) {
- if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) {
- return GIF_INSUFFICIENT_DATA;
- }
- for (index = 0; index < gif->colour_table_size; index++) {
- /* 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.
- */
- unsigned char *entry = (unsigned char *) &gif->
- global_colour_table[index];
-
- entry[0] = gif_data[0]; /* r */
- entry[1] = gif_data[1]; /* g */
- entry[2] = gif_data[2]; /* b */
- entry[3] = 0xff; /* a */
-
- gif_data += 3;
- }
- gif->buffer_position = (gif_data - gif->gif_data);
- } else {
- /* Create a default colour table with the first two
- * colours as black and white
- */
- unsigned int *entry = gif->global_colour_table;
-
- entry[0] = 0x00000000;
- /* Force Alpha channel to opaque */
- ((unsigned char *) entry)[3] = 0xff;
-
- entry[1] = 0xffffffff;
- }
- }
-
- /* Repeatedly try to initialise frames */
- while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING);
-
- /* If there was a memory error tell the caller */
- if ((return_value == GIF_INSUFFICIENT_MEMORY) ||
- (return_value == GIF_DATA_ERROR)) {
- return return_value;
- }
-
- /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a
- * GIF_INSUFFICIENT_FRAME_DATA
- */
- if ((return_value == GIF_INSUFFICIENT_DATA) &&
- (gif->frame_count_partial > 0)) {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
-
- /* Return how many we got */
- return return_value;
+ unsigned char *gif_data;
+ unsigned int index;
+ gif_result return_value;
+
+ /* Initialize values */
+ gif->buffer_size = size;
+ gif->gif_data = data;
+
+ if (gif->lzw_ctx == NULL) {
+ lzw_result res = lzw_context_create(
+ (struct lzw_ctx **)&gif->lzw_ctx);
+ if (res != LZW_OK) {
+ return gif_error_from_lzw(res);
+ }
+ }
+
+ /* Check for sufficient data to be a GIF (6-byte header + 7-byte
+ * logical screen descriptor)
+ */
+ if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) {
+ return GIF_INSUFFICIENT_DATA;
+ }
+
+ /* Get our current processing position */
+ gif_data = gif->gif_data + gif->buffer_position;
+
+ /* See if we should initialise the GIF */
+ if (gif->buffer_position == 0) {
+ /* We want everything to be NULL before we start so we've no
+ * chance of freeing bad pointers (paranoia)
+ */
+ gif->frame_image = NULL;
+ gif->frames = NULL;
+ gif->local_colour_table = NULL;
+ gif->global_colour_table = NULL;
+
+ /* The caller may have been lazy and not reset any values */
+ gif->frame_count = 0;
+ gif->frame_count_partial = 0;
+ gif->decoded_frame = GIF_INVALID_FRAME;
+
+ /* 6-byte GIF file header is:
+ *
+ * +0 3CHARS Signature ('GIF')
+ * +3 3CHARS Version ('87a' or '89a')
+ */
+ if (strncmp((const char *) gif_data, "GIF", 3) != 0) {
+ return GIF_DATA_ERROR;
+ }
+ gif_data += 3;
+
+ /* Ensure GIF reports version 87a or 89a */
+ /*
+ if ((strncmp(gif_data, "87a", 3) != 0) &&
+ (strncmp(gif_data, "89a", 3) != 0))
+ LOG(("Unknown GIF format - proceeding anyway"));
+ */
+ gif_data += 3;
+
+ /* 7-byte Logical Screen Descriptor is:
+ *
+ * +0 SHORT Logical Screen Width
+ * +2 SHORT Logical Screen Height
+ * +4 CHAR __Packed Fields__
+ * 1BIT Global Colour Table Flag
+ * 3BITS Colour Resolution
+ * 1BIT Sort Flag
+ * 3BITS Size of Global Colour Table
+ * +5 CHAR Background Colour Index
+ * +6 CHAR Pixel Aspect Ratio
+ */
+ gif->width = gif_data[0] | (gif_data[1] << 8);
+ gif->height = gif_data[2] | (gif_data[3] << 8);
+ gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK);
+ gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK));
+ gif->background_index = gif_data[5];
+ gif->aspect_ratio = gif_data[6];
+ gif->loop_count = 1;
+ gif_data += 7;
+
+ /* Some broken GIFs report the size as the screen size they
+ * were created in. As such, we detect for the common cases and
+ * set the sizes as 0 if they are found which results in the
+ * GIF being the maximum size of the frames.
+ */
+ if (((gif->width == 640) && (gif->height == 480)) ||
+ ((gif->width == 640) && (gif->height == 512)) ||
+ ((gif->width == 800) && (gif->height == 600)) ||
+ ((gif->width == 1024) && (gif->height == 768)) ||
+ ((gif->width == 1280) && (gif->height == 1024)) ||
+ ((gif->width == 1600) && (gif->height == 1200)) ||
+ ((gif->width == 0) || (gif->height == 0)) ||
+ ((gif->width > 2048) || (gif->height > 2048))) {
+ gif->width = 1;
+ gif->height = 1;
+ }
+
+ /* Allocate some data irrespective of whether we've got any
+ * colour tables. We always get the maximum size in case a GIF
+ * is lying to us. It's far better to give the wrong colours
+ * than to trample over some memory somewhere.
+ */
+ gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int));
+ gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int));
+ if ((gif->global_colour_table == NULL) ||
+ (gif->local_colour_table == NULL)) {
+ gif_finalise(gif);
+ return GIF_INSUFFICIENT_MEMORY;
+ }
+
+ /* Set the first colour to a value that will never occur in
+ * reality so we know if we've processed it
+ */
+ gif->global_colour_table[0] = GIF_PROCESS_COLOURS;
+
+ /* Check if the GIF has no frame data (13-byte header + 1-byte
+ * termination block) Although generally useless, the GIF
+ * specification does not expressly prohibit this
+ */
+ if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) {
+ if (gif_data[0] == GIF_TRAILER) {
+ return GIF_OK;
+ } else {
+ return GIF_INSUFFICIENT_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;
+ }
+
+ /* Do the colour map if we haven't already. As the top byte is always
+ * 0xff or 0x00 depending on the transparency we know if it's been
+ * filled in.
+ */
+ if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) {
+ /* Check for a global colour map signified by bit 7 */
+ if (gif->global_colours) {
+ if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) {
+ return GIF_INSUFFICIENT_DATA;
+ }
+ for (index = 0; index < gif->colour_table_size; index++) {
+ /* 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.
+ */
+ unsigned char *entry = (unsigned char *) &gif->
+ global_colour_table[index];
+
+ entry[0] = gif_data[0]; /* r */
+ entry[1] = gif_data[1]; /* g */
+ entry[2] = gif_data[2]; /* b */
+ entry[3] = 0xff; /* a */
+
+ gif_data += 3;
+ }
+ gif->buffer_position = (gif_data - gif->gif_data);
+ } else {
+ /* Create a default colour table with the first two
+ * colours as black and white
+ */
+ unsigned int *entry = gif->global_colour_table;
+
+ entry[0] = 0x00000000;
+ /* Force Alpha channel to opaque */
+ ((unsigned char *) entry)[3] = 0xff;
+
+ entry[1] = 0xffffffff;
+ }
+ }
+
+ /* Repeatedly try to initialise frames */
+ while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING);
+
+ /* If there was a memory error tell the caller */
+ if ((return_value == GIF_INSUFFICIENT_MEMORY) ||
+ (return_value == GIF_DATA_ERROR)) {
+ return return_value;
+ }
+
+ /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a
+ * GIF_INSUFFICIENT_FRAME_DATA
+ */
+ if ((return_value == GIF_INSUFFICIENT_DATA) &&
+ (gif->frame_count_partial > 0)) {
+ return GIF_INSUFFICIENT_FRAME_DATA;
+ }
+
+ /* Return how many we got */
+ return return_value;
}
/* exported function documented in libnsgif.h */
gif_result gif_decode_frame(gif_animation *gif, unsigned int frame)
{
- return gif_internal_decode_frame(gif, frame, false);
+ return gif_internal_decode_frame(gif, frame, false);
}
/* exported function documented in libnsgif.h */
void gif_finalise(gif_animation *gif)
{
- /* Release all our memory blocks */
- if (gif->frame_image) {
- assert(gif->bitmap_callbacks.bitmap_destroy);
- gif->bitmap_callbacks.bitmap_destroy(gif->frame_image);
- }
-
- gif->frame_image = NULL;
- free(gif->frames);
- gif->frames = NULL;
- free(gif->local_colour_table);
- gif->local_colour_table = NULL;
- free(gif->global_colour_table);
- gif->global_colour_table = NULL;
-
- free(gif->prev_frame);
- gif->prev_frame = NULL;
-
- lzw_context_destroy(gif->lzw_ctx);
- gif->lzw_ctx = NULL;
+ /* Release all our memory blocks */
+ if (gif->frame_image) {
+ assert(gif->bitmap_callbacks.bitmap_destroy);
+ gif->bitmap_callbacks.bitmap_destroy(gif->frame_image);
+ }
+
+ gif->frame_image = NULL;
+ free(gif->frames);
+ gif->frames = NULL;
+ free(gif->local_colour_table);
+ gif->local_colour_table = NULL;
+ free(gif->global_colour_table);
+ gif->global_colour_table = NULL;
+
+ free(gif->prev_frame);
+ gif->prev_frame = NULL;
+
+ lzw_context_destroy(gif->lzw_ctx);
+ gif->lzw_ctx = NULL;
}
diff --git a/src/lzw.c b/src/lzw.c
index 31cf7d4..c865b8d 100644
--- a/src/lzw.c
+++ b/src/lzw.c
@@ -4,6 +4,7 @@
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
+ * Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <assert.h>
@@ -20,6 +21,8 @@
* Decoder for GIF LZW data.
*/
+/** Maximum number of lzw table entries. */
+#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX)
/**
* Context for reading LZW data.
@@ -33,55 +36,63 @@
* Note that an individual LZW code can be split over up to three sub-blocks.
*/
struct lzw_read_ctx {
- const uint8_t *data; /**< Pointer to start of input data */
+ const uint8_t *restrict data; /**< Pointer to start of input data */
uint32_t data_len; /**< Input data length */
uint32_t data_sb_next; /**< Offset to sub-block size */
const uint8_t *sb_data; /**< Pointer to current sub-block in data */
- uint32_t sb_bit; /**< Current bit offset in sub-block */
+ size_t sb_bit; /**< Current bit offset in sub-block */
uint32_t sb_bit_count; /**< Bit count in sub-block */
};
/**
- * LZW dictionary entry.
+ * LZW table entry.
*
- * Records in the dictionary are composed of 1 or more entries.
- * Entries point to previous entries which can be followed to compose
+ * Records in the table are composed of 1 or more entries.
+ * Entries refer to the entry they extend which can be followed to compose
* the complete record. To compose the record in reverse order, take
- * the `last_value` from each entry, and move to the previous entry.
- * If the previous_entry's index is < the current clear_code, then it
+ * the `value` from each entry, and move to the entry it extends.
+ * If the extended entries index is < the current clear_code, then it
* is the last entry in the record.
*/
-struct lzw_dictionary_entry {
- uint8_t last_value; /**< Last value for record ending at entry. */
- uint8_t first_value; /**< First value for entry's record. */
- uint16_t previous_entry; /**< Offset in dictionary to previous entry. */
+struct lzw_table_entry {
+ uint8_t value; /**< Last value for record ending at entry. */
+ uint8_t first; /**< First value in entry's entire record. */
+ uint16_t count; /**< Count of values in this entry's record. */
+ uint16_t extends; /**< Offset in table to previous entry. */
};
/**
* LZW decompression context.
*/
struct lzw_ctx {
- /** Input reading context */
- struct lzw_read_ctx input;
+ struct lzw_read_ctx input; /**< Input reading context */
- uint32_t previous_code; /**< Code read from input previously. */
- uint32_t previous_code_first; /**< First value of previous code. */
+ uint32_t prev_code; /**< Code read from input previously. */
+ uint32_t prev_code_first; /**< First value of previous code. */
+ uint32_t prev_code_count; /**< Total values for previous code. */
- uint32_t initial_code_size; /**< Starting LZW code size. */
- uint32_t current_code_size; /**< Current LZW code size. */
- uint32_t current_code_size_max; /**< Max code value for current size. */
+ uint32_t initial_code_size; /**< Starting LZW code size. */
+
+ uint32_t code_size; /**< Current LZW code size. */
+ uint32_t code_max; /**< Max code value for current code size. */
uint32_t clear_code; /**< Special Clear code value */
uint32_t eoi_code; /**< Special End of Information code value */
- uint32_t current_entry; /**< Next position in table to fill. */
+ uint32_t table_size; /**< Next position in table to fill. */
+
+ uint32_t output_code; /**< Code that has been partially output. */
+ uint32_t output_left; /**< Number of values left for output_code. */
+
+ uint32_t transparency_idx; /**< Index representing transparency. */
+ uint32_t *restrict colour_map; /**< Index to pixel colour mapping */
/** Output value stack. */
- uint8_t stack_base[1 << LZW_CODE_MAX];
+ uint8_t stack_base[LZW_TABLE_ENTRY_MAX];
- /** LZW decode dictionary. Generated during decode. */
- struct lzw_dictionary_entry table[1 << LZW_CODE_MAX];
+ /** LZW code table. Generated during decode. */
+ struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX];
};
@@ -111,7 +122,7 @@ void lzw_context_destroy(struct lzw_ctx *ctx)
* \param[in] ctx LZW reading context, updated on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/
-static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
+static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx)
{
uint32_t block_size;
uint32_t next_block_pos = ctx->data_sb_next;
@@ -152,31 +163,27 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
* \param[out] code_out Returns an LZW code on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/
-static inline lzw_result lzw__next_code(
- struct lzw_read_ctx *ctx,
- uint8_t code_size,
- uint32_t *code_out)
+static inline lzw_result lzw__read_code(
+ struct lzw_read_ctx *restrict ctx,
+ uint32_t code_size,
+ uint32_t *restrict code_out)
{
uint32_t code = 0;
- uint8_t current_bit = ctx->sb_bit & 0x7;
- uint8_t byte_advance = (current_bit + code_size) >> 3;
+ uint32_t current_bit = ctx->sb_bit & 0x7;
- assert(byte_advance <= 2);
-
- if (ctx->sb_bit + code_size <= ctx->sb_bit_count) {
- /* Fast path: code fully inside this sub-block */
+ if (ctx->sb_bit + 24 <= ctx->sb_bit_count) {
+ /* Fast path: read three bytes from this sub-block */
const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
- switch (byte_advance) {
- case 2: code |= data[2] << 16; /* Fall through */
- case 1: code |= data[1] << 8; /* Fall through */
- case 0: code |= data[0] << 0;
- }
+ code |= *data++ << 0;
+ code |= *data++ << 8;
+ code |= *data << 16;
ctx->sb_bit += code_size;
} else {
/* Slow path: code spans sub-blocks */
+ uint8_t byte_advance = (current_bit + code_size) >> 3;
uint8_t byte = 0;
- uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ?
- code_size : (8 - current_bit);
+ uint8_t bits_remaining_0 = (code_size < (8u - current_bit)) ?
+ code_size : (8u - current_bit);
uint8_t bits_remaining_1 = code_size - bits_remaining_0;
uint8_t bits_used[3] = {
[0] = bits_remaining_0,
@@ -184,6 +191,8 @@ static inline lzw_result lzw__next_code(
[2] = bits_remaining_1 - 8,
};
+ assert(byte_advance <= 2);
+
while (true) {
const uint8_t *data = ctx->sb_data;
lzw_result res;
@@ -215,47 +224,38 @@ static inline lzw_result lzw__next_code(
/**
- * Clear LZW code dictionary.
+ * Handle clear code.
*
- * \param[in] ctx LZW reading context, updated.
- * \param[out] stack_pos_out Returns current stack position.
+ * \param[in] ctx LZW reading context, updated.
+ * \param[out] code_out Returns next code after a clear code.
* \return LZW_OK or error code.
*/
-static lzw_result lzw__clear_codes(
+static inline lzw_result lzw__handle_clear(
struct lzw_ctx *ctx,
- const uint8_t ** const stack_pos_out)
+ uint32_t *code_out)
{
uint32_t code;
- uint8_t *stack_pos;
- /* Reset dictionary building context */
- ctx->current_code_size = ctx->initial_code_size + 1;
- ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;;
- ctx->current_entry = (1 << ctx->initial_code_size) + 2;
+ /* Reset table building context */
+ ctx->code_size = ctx->initial_code_size;
+ ctx->code_max = (1 << ctx->initial_code_size) - 1;
+ ctx->table_size = ctx->eoi_code + 1;
/* There might be a sequence of clear codes, so process them all */
do {
- lzw_result res = lzw__next_code(&ctx->input,
- ctx->current_code_size, &code);
+ lzw_result res = lzw__read_code(&ctx->input,
+ ctx->code_size, &code);
if (res != LZW_OK) {
return res;
}
} while (code == ctx->clear_code);
- /* The initial code must be from the initial dictionary. */
+ /* The initial code must be from the initial table. */
if (code > ctx->clear_code) {
return LZW_BAD_ICODE;
}
- /* Record this initial code as "previous" code, needed during decode. */
- ctx->previous_code = code;
- ctx->previous_code_first = code;
-
- /* Reset the stack, and add first non-clear code added as first item. */
- stack_pos = ctx->stack_base;
- *stack_pos++ = code;
-
- *stack_pos_out = stack_pos;
+ *code_out = code;
return LZW_OK;
}
@@ -266,11 +266,15 @@ lzw_result lzw_decode_init(
const uint8_t *compressed_data,
uint32_t compressed_data_len,
uint32_t compressed_data_pos,
- uint8_t code_size,
- const uint8_t ** const stack_base_out,
- const uint8_t ** const stack_pos_out)
+ uint8_t minimum_code_size)
{
- struct lzw_dictionary_entry *table = ctx->table;
+ struct lzw_table_entry *table = ctx->table;
+ lzw_result res;
+ uint32_t code;
+
+ if (minimum_code_size >= LZW_CODE_MAX) {
+ return LZW_BAD_ICODE;
+ }
/* Initialise the input reading context */
ctx->input.data = compressed_data;
@@ -280,98 +284,291 @@ lzw_result lzw_decode_init(
ctx->input.sb_bit = 0;
ctx->input.sb_bit_count = 0;
- /* Initialise the dictionary building context */
- ctx->initial_code_size = code_size;
+ /* Initialise the table building context */
+ ctx->initial_code_size = minimum_code_size + 1;
+
+ ctx->clear_code = (1 << minimum_code_size) + 0;
+ ctx->eoi_code = (1 << minimum_code_size) + 1;
- ctx->clear_code = (1 << code_size) + 0;
- ctx->eoi_code = (1 << code_size) + 1;
+ ctx->output_left = 0;
- /* Initialise the standard dictionary entries */
+ /* Initialise the standard table entries */
for (uint32_t i = 0; i < ctx->clear_code; ++i) {
- table[i].first_value = i;
- table[i].last_value = i;
+ table[i].first = i;
+ table[i].value = i;
+ table[i].count = 1;
+ }
+
+ res = lzw__handle_clear(ctx, &code);
+ if (res != LZW_OK) {
+ return res;
}
- *stack_base_out = ctx->stack_base;
- return lzw__clear_codes(ctx, stack_pos_out);
+ /* Store details of this code as "previous code" to the context. */
+ ctx->prev_code_first = ctx->table[code].first;
+ ctx->prev_code_count = ctx->table[code].count;
+ ctx->prev_code = code;
+
+ /* Add code to context for immediate output. */
+ ctx->output_code = code;
+ ctx->output_left = 1;
+
+ return LZW_OK;
+}
+
+/**
+ * Create new table entry.
+ *
+ * \param[in] ctx LZW reading context, updated.
+ * \param[in] code Last value code for new table entry.
+ */
+static inline void lzw__table_add_entry(
+ struct lzw_ctx *ctx,
+ uint32_t code)
+{
+ struct lzw_table_entry *entry = &ctx->table[ctx->table_size];
+
+ entry->value = code;
+ entry->first = ctx->prev_code_first;
+ entry->count = ctx->prev_code_count + 1;
+ entry->extends = ctx->prev_code;
+
+ ctx->table_size++;
}
+typedef uint32_t (*lzw_writer_fn)(
+ struct lzw_ctx *ctx,
+ void *restrict output,
+ uint32_t length,
+ uint32_t used,
+ uint32_t code,
+ uint32_t left);
-/* Exported function, documented in lzw.h */
-lzw_result lzw_decode(struct lzw_ctx *ctx,
- const uint8_t ** const stack_pos_out)
+/**
+ * Get the next LZW code and write its value(s) to output buffer.
+ *
+ * \param[in] ctx LZW reading context, updated.
+ * \param[in] output Array to write output values into.
+ * \param[in] length Size of output array.
+ * \param[in] write_pixels Function for writing pixels to output.
+ * \param[in,out] used Number of values written. Updated on exit.
+ * \return LZW_OK on success, or appropriate error code otherwise.
+ */
+static inline lzw_result lzw__decode(struct lzw_ctx *ctx,
+ void *restrict output,
+ uint32_t length,
+ lzw_writer_fn write_pixels,
+ uint32_t *restrict used)
{
lzw_result res;
- uint32_t code_new;
- uint32_t code_out;
- uint8_t last_value;
- uint8_t *stack_pos = ctx->stack_base;
- uint32_t clear_code = ctx->clear_code;
- uint32_t current_entry = ctx->current_entry;
- struct lzw_dictionary_entry * const table = ctx->table;
+ uint32_t code;
/* Get a new code from the input */
- res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new);
+ res = lzw__read_code(&ctx->input, ctx->code_size, &code);
if (res != LZW_OK) {
return res;
}
/* Handle the new code */
- if (code_new == clear_code) {
- /* Got Clear code */
- return lzw__clear_codes(ctx, stack_pos_out);
-
- } else if (code_new == ctx->eoi_code) {
+ if (code == ctx->eoi_code) {
/* Got End of Information code */
return LZW_EOI_CODE;
- } else if (code_new > current_entry) {
+ } else if (code > ctx->table_size) {
/* Code is invalid */
return LZW_BAD_CODE;
- } else if (code_new < current_entry) {
- /* Code is in table */
- code_out = code_new;
- last_value = table[code_new].first_value;
+ } else if (code == ctx->clear_code) {
+ res = lzw__handle_clear(ctx, &code);
+ if (res != LZW_OK) {
+ return res;
+ }
+
+ } else if (ctx->table_size < LZW_TABLE_ENTRY_MAX) {
+ uint32_t size = ctx->table_size;
+ lzw__table_add_entry(ctx, (code < size) ?
+ ctx->table[code].first :
+ ctx->prev_code_first);
+
+ /* Ensure code size is increased, if needed. */
+ if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) {
+ ctx->code_size++;
+ ctx->code_max = (1 << ctx->code_size) - 1;
+ }
+ }
+
+ *used += write_pixels(ctx, output, length, *used, code,
+ ctx->table[code].count);
+
+ /* Store details of this code as "previous code" to the context. */
+ ctx->prev_code_first = ctx->table[code].first;
+ ctx->prev_code_count = ctx->table[code].count;
+ ctx->prev_code = code;
+
+ return LZW_OK;
+}
+
+/**
+ * Write values for this code to the output stack.
+ *
+ * If there isn't enough space in the output stack, this function will write
+ * the as many as it can into the output. If `ctx->output_left > 0` after
+ * this call, then there is more data for this code left to output. The code
+ * is stored to the context as `ctx->output_code`.
+ *
+ * \param[in] ctx LZW reading context, updated.
+ * \param[in] output Array to write output values into.
+ * \param[in] length Size of output array.
+ * \param[in] used Current position in output array.
+ * \param[in] code LZW code to output values for.
+ * \param[in] left Number of values remaining to output for this value.
+ * \return Number of pixel values written.
+ */
+static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx,
+ void *restrict output,
+ uint32_t length,
+ uint32_t used,
+ uint32_t code,
+ uint32_t left)
+{
+ uint8_t *restrict output_pos = (uint8_t *)output + used;
+ const struct lzw_table_entry * const table = ctx->table;
+ uint32_t space = length - used;
+ uint32_t count = left;
+
+ if (count > space) {
+ left = count - space;
+ count = space;
} else {
- /* Code not in table */
- *stack_pos++ = ctx->previous_code_first;
- code_out = ctx->previous_code;
- last_value = ctx->previous_code_first;
+ left = 0;
+ }
+
+ ctx->output_code = code;
+ ctx->output_left = left;
+
+ /* Skip over any values we don't have space for. */
+ for (unsigned i = left; i != 0; i--) {
+ const struct lzw_table_entry *entry = table + code;
+ code = entry->extends;
}
- /* Add to the dictionary, only if there's space */
- if (current_entry < (1 << LZW_CODE_MAX)) {
- struct lzw_dictionary_entry *entry = table + current_entry;
- entry->last_value = last_value;
- entry->first_value = ctx->previous_code_first;
- entry->previous_entry = ctx->previous_code;
- ctx->current_entry++;
+ output_pos += count;
+ for (unsigned i = count; i != 0; i--) {
+ const struct lzw_table_entry *entry = table + code;
+ *--output_pos = entry->value;
+ code = entry->extends;
}
- /* Ensure code size is increased, if needed. */
- if (current_entry == ctx->current_code_size_max) {
- if (ctx->current_code_size < LZW_CODE_MAX) {
- ctx->current_code_size++;
- ctx->current_code_size_max =
- (1 << ctx->current_code_size) - 1;
+ return count;
+}
+
+/* Exported function, documented in lzw.h */
+lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
+ const uint8_t *restrict *const restrict data,
+ uint32_t *restrict used)
+{
+ *used = 0;
+ *data = ctx->stack_base;
+
+ if (ctx->output_left != 0) {
+ *used += lzw__write_pixels(ctx,
+ ctx->stack_base, sizeof(ctx->stack_base), *used,
+ ctx->output_code, ctx->output_left);
+ }
+
+ while (*used != sizeof(ctx->stack_base)) {
+ lzw_result res = lzw__decode(ctx,
+ ctx->stack_base, sizeof(ctx->stack_base),
+ lzw__write_pixels, used);
+ if (res != LZW_OK) {
+ return res;
}
}
- /* Store details of this code as "previous code" to the context. */
- ctx->previous_code_first = table[code_new].first_value;
- ctx->previous_code = code_new;
-
- /* Put rest of data for this code on output stack.
- * Note, in the case of "code not in table", the last entry of the
- * current code has already been placed on the stack above. */
- while (code_out > clear_code) {
- struct lzw_dictionary_entry *entry = table + code_out;
- *stack_pos++ = entry->last_value;
- code_out = entry->previous_entry;
+ return LZW_OK;
+}
+
+/**
+ * Write colour mapped values for this code to the output stack.
+ *
+ * If there isn't enough space in the output stack, this function will write
+ * the as many as it can into the output. If `ctx->output_left > 0` after
+ * this call, then there is more data for this code left to output. The code
+ * is stored to the context as `ctx->output_code`.
+ *
+ * \param[in] ctx LZW reading context, updated.
+ * \param[in] output Array to write output values into.
+ * \param[in] length Size of output array.
+ * \param[in] used Current position in output array.
+ * \param[in] code LZW code to output values for.
+ * \param[in] left Number of values remaining to output for this value.
+ * \return Number of pixel values written.
+ */
+static inline uint32_t lzw__write_pixels_map(struct lzw_ctx *ctx,
+ void *restrict buffer,
+ uint32_t length,
+ uint32_t used,
+ uint32_t code,
+ uint32_t left)
+{
+ uint32_t *restrict stack_pos = (uint32_t *)buffer + used;
+ const struct lzw_table_entry * const table = ctx->table;
+ uint32_t space = length - used;
+ uint32_t count = left;
+
+ if (count > space) {
+ left = count - space;
+ count = space;
+ } else {
+ left = 0;
+ }
+
+ ctx->output_code = code;
+ ctx->output_left = left;
+
+ for (unsigned i = left; i != 0; i--) {
+ const struct lzw_table_entry *entry = table + code;
+ code = entry->extends;
+ }
+
+ stack_pos += count;
+ for (unsigned i = count; i != 0; i--) {
+ const struct lzw_table_entry *entry = table + code;
+ --stack_pos;
+ if (entry->value != ctx->transparency_idx) {
+ *stack_pos = ctx->colour_map[entry->value];
+ }
+ code = entry->extends;
+ }
+
+ return count;
+}
+
+/* Exported function, documented in lzw.h */
+lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx,
+ uint32_t transparency_idx,
+ uint32_t *restrict colour_map,
+ uint32_t *restrict data,
+ uint32_t length,
+ uint32_t *restrict used)
+{
+ *used = 0;
+
+ ctx->transparency_idx = transparency_idx;
+ ctx->colour_map = colour_map;
+
+ if (ctx->output_left != 0) {
+ *used += lzw__write_pixels_map(ctx, data, length, *used,
+ ctx->output_code, ctx->output_left);
+ }
+
+ while (*used != sizeof(ctx->stack_base)) {
+ lzw_result res = lzw__decode(ctx, data, length,
+ lzw__write_pixels_map, used);
+ if (res != LZW_OK) {
+ return res;
+ }
}
- *stack_pos++ = table[code_out].last_value;
- *stack_pos_out = stack_pos;
return LZW_OK;
}
diff --git a/src/lzw.h b/src/lzw.h
index 385b425..a227163 100644
--- a/src/lzw.h
+++ b/src/lzw.h
@@ -65,11 +65,8 @@ void lzw_context_destroy(
* \param[in] compressed_data_len Byte length of compressed data.
* \param[in] compressed_data_pos Start position in data. Must be position
* of a size byte at sub-block start.
- * \param[in] code_size The initial LZW code size to use.
+ * \param[in] minimum_code_size The LZW Minimum Code Size.
* \param[out] stack_base_out Returns base of decompressed data stack.
- * \param[out] stack_pos_out Returns current stack position.
- * There are `stack_pos_out - stack_base_out`
- * current stack entries.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode_init(
@@ -77,29 +74,46 @@ lzw_result lzw_decode_init(
const uint8_t *compressed_data,
uint32_t compressed_data_len,
uint32_t compressed_data_pos,
- uint8_t code_size,
- const uint8_t ** const stack_base_out,
- const uint8_t ** const stack_pos_out);
+ uint8_t minimum_code_size);
/**
- * Fill the LZW stack with decompressed data
+ * Read input codes until end of lzw context owned output buffer.
*
- * Ensure anything on the stack is used before calling this, as anything
- * on the stack before this call will be trampled.
+ * Ensure anything in output is used before calling this, as anything
+ * on the there before this call will be trampled.
*
- * Caller does not own `stack_pos_out`.
- *
- * \param[in] ctx LZW reading context, updated.
- * \param[out] stack_pos_out Returns current stack position.
- * Use with `stack_base_out` value from previous
- * lzw_decode_init() call.
- * There are `stack_pos_out - stack_base_out`
- * current stack entries.
+ * \param[in] ctx LZW reading context, updated.
+ * \param[out] data Returns pointer to array of output values.
+ * \param[out] used Returns the number of values written to data.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
-lzw_result lzw_decode(
- struct lzw_ctx *ctx,
- const uint8_t ** const stack_pos_out);
+lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
+ const uint8_t *restrict *const restrict data,
+ uint32_t *restrict used);
+/**
+ * Read LZW codes into client buffer, mapping output to colours.
+ *
+ * Ensure anything in output is used before calling this, as anything
+ * on the there before this call will be trampled.
+ *
+ * For transparency to work correctly, the given client buffer must have
+ * the values from the previous frame. The transparency_idx should be a value
+ * of 256 or above, if the frame does not have transparency.
+ *
+ * \param[in] ctx LZW reading context, updated.
+ * \param[in] transparency_idx Index representing transparency.
+ * \param[in] colour_map Index to pixel colour mapping
+ * \param[in] data Client buffer to fill with colour mapped values.
+ * \param[in] length Size of output array.
+ * \param[out] used Returns the number of values written to data.
+ * \return LZW_OK on success, or appropriate error code otherwise.
+ */
+lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx,
+ uint32_t transparency_idx,
+ uint32_t *restrict colour_table,
+ uint32_t *restrict data,
+ uint32_t length,
+ uint32_t *restrict used);
#endif
diff --git a/test/decode_gif.c b/test/decode_gif.c
index 619be29..64387ef 100644
--- a/test/decode_gif.c
+++ b/test/decode_gif.c
@@ -161,6 +161,10 @@ static void write_ppm(FILE* fh, const char *name, gif_animation *gif,
if (code != GIF_OK)
warning("gif_decode_frame", code);
+ if (!gif->frames[i].display) {
+ continue;
+ }
+
if (!no_write) {
fprintf(fh, "# frame %u:\n", i);
image = (unsigned char *) gif->frame_image;