libnsgif: branch master updated. release/0.2.1-154-g5398ee4
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/5398ee4f08ffeab3778d...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/5398ee4f08ffeab3778dc8...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/5398ee4f08ffeab3778dc8f0...
The branch, master has been updated
via 5398ee4f08ffeab3778dc8f014f49f73c8e9eb43 (commit)
from 464bec1bbec32978a41b0a0f011711df428117c3 (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=5398ee4f08ffeab377...
commit 5398ee4f08ffeab3778dc8f014f49f73c8e9eb43
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Squash warning about increase of alignment of pointer type.
diff --git a/src/gif.c b/src/gif.c
index 467ff90..3c40685 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -203,7 +203,7 @@ static inline uint32_t* nsgif__bitmap_get(
/* Get the frame data */
assert(gif->bitmap.get_buffer);
- return (uint32_t *)gif->bitmap.get_buffer(gif->frame_image);
+ return (void *)gif->bitmap.get_buffer(gif->frame_image);
}
/**
-----------------------------------------------------------------------
Summary of changes:
src/gif.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/gif.c b/src/gif.c
index 467ff90..3c40685 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -203,7 +203,7 @@ static inline uint32_t* nsgif__bitmap_get(
/* Get the frame data */
assert(gif->bitmap.get_buffer);
- return (uint32_t *)gif->bitmap.get_buffer(gif->frame_image);
+ return (void *)gif->bitmap.get_buffer(gif->frame_image);
}
/**
--
NetSurf GIF Decoder
1 year, 7 months
libnsgif: branch master updated. release/0.2.1-153-g464bec1
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/464bec1bbec32978a41b...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/464bec1bbec32978a41b0a...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/464bec1bbec32978a41b0a0f...
The branch, master has been updated
via 464bec1bbec32978a41b0a0f011711df428117c3 (commit)
from 41d8bcf828af44c775454e46ce751f9ac1a3684e (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=464bec1bbec32978a4...
commit 464bec1bbec32978a41b0a0f011711df428117c3
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Docs: Add note about source data lifetime to README.
diff --git a/README.md b/README.md
index d821ac6..cb380c3 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,11 @@ function has returned `NSGIF_OK` it has enough data to display at least one
frame. The early frames can be decoded before the later frames are scanned.
Frames have to be scanned before they can be decoded.
+> **Note**: The client must not free the data until after calling
+> `nsgif_destroy()`. You can move the data, e.g. if you realloc to a bigger
+> buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer
+> before making any other calls against that nsgif object.
+
To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
and then call `nsgif_frame_decode()` for each frame, and manage the animation,
and non-displayable frames yourself, or you can use the helper function,
-----------------------------------------------------------------------
Summary of changes:
README.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/README.md b/README.md
index d821ac6..cb380c3 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,11 @@ function has returned `NSGIF_OK` it has enough data to display at least one
frame. The early frames can be decoded before the later frames are scanned.
Frames have to be scanned before they can be decoded.
+> **Note**: The client must not free the data until after calling
+> `nsgif_destroy()`. You can move the data, e.g. if you realloc to a bigger
+> buffer. Just be sure to call `nsgif_data_scan()` again with the new pointer
+> before making any other calls against that nsgif object.
+
To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
and then call `nsgif_frame_decode()` for each frame, and manage the animation,
and non-displayable frames yourself, or you can use the helper function,
--
NetSurf GIF Decoder
1 year, 7 months
netsurf: branch master updated. release/3.10-159-g2e9ef8f
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/netsurf.git/shortlog/2e9ef8feae321d9a8463c...
...commit http://git.netsurf-browser.org/netsurf.git/commit/2e9ef8feae321d9a8463c81...
...tree http://git.netsurf-browser.org/netsurf.git/tree/2e9ef8feae321d9a8463c816a...
The branch, master has been updated
via 2e9ef8feae321d9a8463c816a356eb73d789b6c7 (commit)
from d92b2696295e18146ff5c8fd15bc6ea198749f28 (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 -----------------------------------------------------------------
-----------------------------------------------------------------------
Summary of changes:
content/handlers/image/gif.c | 366 ++++++++++++++++++------------------------
1 file changed, 160 insertions(+), 206 deletions(-)
diff --git a/content/handlers/image/gif.c b/content/handlers/image/gif.c
index e2a0ca5..0d4506a 100644
--- a/content/handlers/image/gif.c
+++ b/content/handlers/image/gif.c
@@ -34,7 +34,8 @@
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
-#include <libnsgif.h>
+
+#include <nsgif.h>
#include "utils/utils.h"
#include "utils/messages.h"
@@ -51,13 +52,31 @@
#include "image/image.h"
#include "image/gif.h"
-typedef struct nsgif_content {
+typedef struct gif_content {
struct content base;
- struct gif_animation *gif; /**< GIF animation data */
- int current_frame; /**< current frame to display [0...(max-1)] */
-} nsgif_content;
+ nsgif_t *gif; /**< GIF animation data */
+ uint32_t current_frame; /**< current frame to display [0...(max-1)] */
+} gif_content;
+
+static inline nserror gif__nsgif_error_to_ns(nsgif_error gif_res)
+{
+ nserror err;
+
+ switch (gif_res) {
+ case NSGIF_OK:
+ err = NSERROR_OK;
+ break;
+ case NSGIF_ERR_OOM:
+ err = NSERROR_NOMEM;
+ break;
+ default:
+ err = NSERROR_GIF_ERROR;
+ break;
+ }
+ return err;
+}
/**
* Callback for libnsgif; forwards the call to bitmap_create()
@@ -66,44 +85,42 @@ typedef struct nsgif_content {
* \param height width of image in pixels
* \return an opaque struct bitmap, or NULL on memory exhaustion
*/
-static void *nsgif_bitmap_create(int width, int height)
+static void *gif_bitmap_create(int width, int height)
{
return guit->bitmap->create(width, height, BITMAP_NEW);
}
-
-static nserror nsgif_create_gif_data(nsgif_content *c)
+static nserror gif_create_gif_data(gif_content *c)
{
- gif_bitmap_callback_vt gif_bitmap_callbacks = {
- .bitmap_create = nsgif_bitmap_create,
- .bitmap_destroy = guit->bitmap->destroy,
- .bitmap_get_buffer = guit->bitmap->get_buffer,
- .bitmap_set_opaque = guit->bitmap->set_opaque,
- .bitmap_test_opaque = guit->bitmap->test_opaque,
- .bitmap_modified = guit->bitmap->modified
+ nsgif_error gif_res;
+ const nsgif_bitmap_cb_vt gif_bitmap_callbacks = {
+ .create = gif_bitmap_create,
+ .destroy = guit->bitmap->destroy,
+ .get_buffer = guit->bitmap->get_buffer,
+ .set_opaque = guit->bitmap->set_opaque,
+ .test_opaque = guit->bitmap->test_opaque,
+ .modified = guit->bitmap->modified
};
- /* Initialise our data structure */
- c->gif = calloc(sizeof(gif_animation), 1);
- if (c->gif == NULL) {
- content_broadcast_error(&c->base, NSERROR_NOMEM, NULL);
- return NSERROR_NOMEM;
+ gif_res = nsgif_create(&gif_bitmap_callbacks, &c->gif);
+ if (gif_res != NSGIF_OK) {
+ nserror err = gif__nsgif_error_to_ns(gif_res);
+ content_broadcast_error(&c->base, err, NULL);
+ return err;
}
- gif_create(c->gif, &gif_bitmap_callbacks);
+
return NSERROR_OK;
}
-
-
-static nserror nsgif_create(const content_handler *handler,
- lwc_string *imime_type, const struct http_parameter *params,
+static nserror gif_create(const content_handler *handler,
+ lwc_string *imime_type, const struct http_parameter *params,
llcache_handle *llcache, const char *fallback_charset,
bool quirks, struct content **c)
{
- nsgif_content *result;
+ gif_content *result;
nserror error;
- result = calloc(1, sizeof(nsgif_content));
+ result = calloc(1, sizeof(gif_content));
if (result == NULL)
return NSERROR_NOMEM;
@@ -114,7 +131,7 @@ static nserror nsgif_create(const content_handler *handler,
return error;
}
- error = nsgif_create_gif_data(result);
+ error = gif_create_gif_data(result);
if (error != NSERROR_OK) {
free(result);
return error;
@@ -126,99 +143,65 @@ static nserror nsgif_create(const content_handler *handler,
}
/**
+ * Scheduler callback. Performs any necessary animation.
+ *
+ * \param p The content to animate
+*/
+static void gif_animate_cb(void *p);
+
+/**
* Performs any necessary animation.
*
* \param p The content to animate
*/
-static void nsgif_animate(void *p)
+static nserror gif__animate(gif_content *gif, bool redraw)
{
- nsgif_content *gif = p;
- union content_msg_data data;
- int delay;
- int f;
-
- /* Advance by a frame, updating the loop count accordingly */
- gif->current_frame++;
- if (gif->current_frame == (int)gif->gif->frame_count_partial) {
- gif->current_frame = 0;
-
- /* A loop count of 0 has a special meaning of infinite */
- if (gif->gif->loop_count != 0) {
- gif->gif->loop_count--;
- if (gif->gif->loop_count == 0) {
- gif->current_frame =
- gif->gif->frame_count_partial - 1;
- gif->gif->loop_count = -1;
- }
- }
+ nsgif_error gif_res;
+ nsgif_rect_t rect;
+ uint32_t delay;
+ uint32_t f;
+
+ gif_res = nsgif_frame_prepare(gif->gif, &rect, &delay, &f);
+ if (gif_res != NSGIF_OK) {
+ return gif__nsgif_error_to_ns(gif_res);
}
+ gif->current_frame = f;
+
/* Continue animating if we should */
- if (gif->gif->loop_count >= 0) {
- delay = gif->gif->frames[gif->current_frame].frame_delay;
- if (delay <= 1) {
- /* Assuming too fast to be intended, set default. */
- delay = 10;
- }
- guit->misc->schedule(delay * 10, nsgif_animate, gif);
+ if (nsoption_bool(animate_images) && delay != NSGIF_INFINITE) {
+ guit->misc->schedule(delay * 10, gif_animate_cb, gif);
}
- if ((!nsoption_bool(animate_images)) ||
- (!gif->gif->frames[gif->current_frame].display)) {
- return;
- }
+ if (redraw) {
+ union content_msg_data data;
- /* area within gif to redraw */
- f = gif->current_frame;
- data.redraw.x = gif->gif->frames[f].redraw_x;
- data.redraw.y = gif->gif->frames[f].redraw_y;
- data.redraw.width = gif->gif->frames[f].redraw_width;
- data.redraw.height = gif->gif->frames[f].redraw_height;
-
- /* redraw background (true) or plot on top (false) */
- if (gif->current_frame > 0) {
- /* previous frame needed clearing: expand the redraw area to
- * cover it */
- if (gif->gif->frames[f - 1].redraw_required) {
- if (data.redraw.x >
- (int)(gif->gif->frames[f - 1].redraw_x)) {
- data.redraw.width += data.redraw.x -
- gif->gif->frames[f - 1].redraw_x;
- data.redraw.x =
- gif->gif->frames[f - 1].redraw_x;
- }
- if (data.redraw.y >
- (int)(gif->gif->frames[f - 1].redraw_y)) {
- data.redraw.height += (data.redraw.y -
- gif->gif->frames[f - 1].redraw_y);
- data.redraw.y =
- gif->gif->frames[f - 1].redraw_y;
- }
- if ((int)(gif->gif->frames[f - 1].redraw_x +
- gif->gif->frames[f - 1].redraw_width) >
- (data.redraw.x + data.redraw.width))
- data.redraw.width =
- gif->gif->frames[f - 1].redraw_x -
- data.redraw.x +
- gif->gif->frames[f - 1].redraw_width;
- if ((int)(gif->gif->frames[f - 1].redraw_y +
- gif->gif->frames[f - 1].redraw_height) >
- (data.redraw.y + data.redraw.height))
- data.redraw.height =
- gif->gif->frames[f - 1].redraw_y -
- data.redraw.y +
- gif->gif->frames[f - 1].redraw_height;
- }
+ /* area within gif to redraw */
+ data.redraw.x = rect.x0;
+ data.redraw.y = rect.y0;
+ data.redraw.width = rect.x1 - rect.x0;
+ data.redraw.height = rect.y1 - rect.y0;
+
+ content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data);
}
- content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data);
+ return NSERROR_OK;
+}
+
+static void gif_animate_cb(void *p)
+{
+ gif_content *gif = p;
+
+ gif__animate(gif, true);
}
-static bool nsgif_convert(struct content *c)
+static bool gif_convert(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
- int res;
+ gif_content *gif = (gif_content *) c;
+ const nsgif_info_t *gif_info;
const uint8_t *data;
+ nsgif_error gif_err;
+ nserror err;
size_t size;
char *title;
@@ -226,37 +209,27 @@ static bool nsgif_convert(struct content *c)
data = content__get_source_data(c, &size);
/* Initialise the GIF */
- do {
- res = gif_initialise(gif->gif, size, (unsigned char *) data);
- if (res != GIF_OK && res != GIF_WORKING &&
- res != GIF_INSUFFICIENT_FRAME_DATA) {
- nserror error = NSERROR_UNKNOWN;
- switch (res) {
- case GIF_FRAME_DATA_ERROR:
- case GIF_INSUFFICIENT_DATA:
- case GIF_DATA_ERROR:
- error = NSERROR_GIF_ERROR;
- break;
- case GIF_INSUFFICIENT_MEMORY:
- error = NSERROR_NOMEM;
- break;
- }
- content_broadcast_error(c, error, NULL);
- return false;
- }
- } while (res != GIF_OK && res != GIF_INSUFFICIENT_FRAME_DATA);
+ gif_err = nsgif_data_scan(gif->gif, size, data);
+ if (gif_err != NSGIF_OK) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, nsgif_strerror(gif_err));
+ return false;
+ }
+
+ gif_info = nsgif_get_info(gif->gif);
+ assert(gif_info != NULL);
/* Abort on bad GIFs */
- if ((gif->gif->frame_count_partial == 0) || (gif->gif->width == 0) ||
- (gif->gif->height == 0)) {
- content_broadcast_error(c, NSERROR_GIF_ERROR, NULL);
+ if (gif_info->height == 0) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, "Zero height image.");
return false;
}
/* Store our content width, height and calculate size */
- c->width = gif->gif->width;
- c->height = gif->gif->height;
- c->size += (gif->gif->width * gif->gif->height * 4) + 16 + 44;
+ c->width = gif_info->width;
+ c->height = gif_info->height;
+ c->size += (gif_info->width * gif_info->height * 4) + 16 + 44;
/* set title text */
title = messages_get_buff("GIFTitle",
@@ -267,12 +240,11 @@ static bool nsgif_convert(struct content *c)
free(title);
}
- /* Schedule the animation if we have one */
- gif->current_frame = 0;
- if (gif->gif->frame_count_partial > 1)
- guit->misc->schedule(gif->gif->frames[0].frame_delay * 10,
- nsgif_animate,
- c);
+ err = gif__animate(gif, false);
+ if (err != NSERROR_OK) {
+ content_broadcast_error(c, NSERROR_GIF_ERROR, NULL);
+ return false;
+ }
/* Exit as a success */
content_set_ready(c);
@@ -283,68 +255,51 @@ static bool nsgif_convert(struct content *c)
return true;
}
-
/**
* Updates the GIF bitmap to display the current frame
*
* \param gif The gif context to update.
- * \return GIF_OK on success else apropriate error code.
+ * \return NSGIF_OK on success else apropriate error code.
*/
-static gif_result nsgif_get_frame(nsgif_content *gif)
+static nsgif_error gif_get_frame(gif_content *gif,
+ nsgif_bitmap_t **bitmap)
{
- int previous_frame, current_frame, frame;
- gif_result res = GIF_OK;
-
- current_frame = gif->current_frame;
+ uint32_t current_frame = gif->current_frame;
if (!nsoption_bool(animate_images)) {
current_frame = 0;
}
- if (current_frame < gif->gif->decoded_frame) {
- previous_frame = 0;
- } else {
- previous_frame = gif->gif->decoded_frame + 1;
- }
-
- for (frame = previous_frame; frame <= current_frame; frame++) {
- res = gif_decode_frame(gif->gif, frame);
- }
-
- return res;
+ return nsgif_frame_decode(gif->gif, current_frame, bitmap);
}
-static bool nsgif_redraw(struct content *c, struct content_redraw_data *data,
+static bool gif_redraw(struct content *c, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK) {
- return false;
- }
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return false;
}
- return image_bitmap_plot(gif->gif->frame_image, data, clip, ctx);
+ return image_bitmap_plot(bitmap, data, clip, ctx);
}
-
-static void nsgif_destroy(struct content *c)
+static void gif_destroy(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
/* Free all the associated memory buffers */
- guit->misc->schedule(-1, nsgif_animate, c);
- gif_finalise(gif->gif);
- free(gif->gif);
+ guit->misc->schedule(-1, gif_animate_cb, c);
+ nsgif_destroy(gif->gif);
}
-
-static nserror nsgif_clone(const struct content *old, struct content **newc)
+static nserror gif_clone(const struct content *old, struct content **newc)
{
- nsgif_content *gif;
+ gif_content *gif;
nserror error;
- gif = calloc(1, sizeof(nsgif_content));
+ gif = calloc(1, sizeof(gif_content));
if (gif == NULL)
return NSERROR_NOMEM;
@@ -355,7 +310,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
}
/* Simply replay creation and conversion of content */
- error = nsgif_create_gif_data(gif);
+ error = gif_create_gif_data(gif);
if (error != NSERROR_OK) {
content_destroy(&gif->base);
return error;
@@ -363,7 +318,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
if (old->status == CONTENT_STATUS_READY ||
old->status == CONTENT_STATUS_DONE) {
- if (nsgif_convert(&gif->base) == false) {
+ if (gif_convert(&gif->base) == false) {
content_destroy(&gif->base);
return NSERROR_CLONE_FAILED;
}
@@ -374,9 +329,9 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
return NSERROR_OK;
}
-static void nsgif_add_user(struct content *c)
+static void gif_add_user(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
/* Ensure this content has already been converted.
* If it hasn't, the animation will start at the conversion phase instead. */
@@ -384,67 +339,66 @@ static void nsgif_add_user(struct content *c)
if (content_count_users(c) == 1) {
/* First user, and content already converted, so start the animation. */
- if (gif->gif->frame_count_partial > 1) {
- guit->misc->schedule(gif->gif->frames[0].frame_delay * 10,
- nsgif_animate, c);
+ if (nsgif_reset(gif->gif) == NSGIF_OK) {
+ gif__animate(gif, true);
}
}
}
-static void nsgif_remove_user(struct content *c)
+static void gif_remove_user(struct content *c)
{
if (content_count_users(c) == 1) {
/* Last user is about to be removed from this content, so stop the animation. */
- guit->misc->schedule(-1, nsgif_animate, c);
+ guit->misc->schedule(-1, gif_animate_cb, c);
}
}
-static void *nsgif_get_internal(const struct content *c, void *context)
+static nsgif_bitmap_t *gif_get_bitmap(
+ const struct content *c, void *context)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK)
- return NULL;
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return NULL;
}
- return gif->gif->frame_image;
+ return bitmap;
}
-static content_type nsgif_content_type(void)
+static content_type gif_content_type(void)
{
return CONTENT_IMAGE;
}
-static bool nsgif_content_is_opaque(struct content *c)
+static bool gif_content_is_opaque(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK) {
- return false;
- }
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return false;
}
- return guit->bitmap->get_opaque(gif->gif->frame_image);
+ return guit->bitmap->get_opaque(bitmap);
}
-static const content_handler nsgif_content_handler = {
- .create = nsgif_create,
- .data_complete = nsgif_convert,
- .destroy = nsgif_destroy,
- .redraw = nsgif_redraw,
- .clone = nsgif_clone,
- .add_user = nsgif_add_user,
- .remove_user = nsgif_remove_user,
- .get_internal = nsgif_get_internal,
- .type = nsgif_content_type,
- .is_opaque = nsgif_content_is_opaque,
+static const content_handler gif_content_handler = {
+ .create = gif_create,
+ .data_complete = gif_convert,
+ .destroy = gif_destroy,
+ .redraw = gif_redraw,
+ .clone = gif_clone,
+ .add_user = gif_add_user,
+ .remove_user = gif_remove_user,
+ .get_internal = gif_get_bitmap,
+ .type = gif_content_type,
+ .is_opaque = gif_content_is_opaque,
.no_share = false,
};
-static const char *nsgif_types[] = {
+static const char *gif_types[] = {
"image/gif"
};
-CONTENT_FACTORY_REGISTER_TYPES(nsgif, nsgif_types, nsgif_content_handler);
+CONTENT_FACTORY_REGISTER_TYPES(nsgif, gif_types, gif_content_handler);
--
NetSurf Browser
1 year, 7 months
libnsgif: branch master updated. release/0.2.1-152-g41d8bcf
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/41d8bcf828af44c77545...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/41d8bcf828af44c775454e...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/41d8bcf828af44c775454e46...
The branch, master has been updated
via 41d8bcf828af44c775454e46ce751f9ac1a3684e (commit)
via 7ee51c0f6025238640cfa55faa36cd73e12489c7 (commit)
via a0025eda79e5f6b7f0ae23e7a85fd947dc847726 (commit)
via bc358088b2b074f09e638f101d2a12b3632a0d73 (commit)
via 4360a768898cc7a43715926e2e8040cb9748bead (commit)
via c800fec625521c9cb791d69933c8084e390c2bd0 (commit)
via ba2037410035d5b95e7458576654f8122ec581ac (commit)
via 370c2a783b67350143c18a07463835887d2b1847 (commit)
via 84a9edb126121cd145295e48a5f0a98de4aab708 (commit)
via ff86c40667cdaa2a535c992f908ba15912d93d59 (commit)
via a0b9fcb4fa4c53319726044c8278a551293af817 (commit)
via dde30d3d4134c46439fb1984eeb88ac2d843fd60 (commit)
via d7a746a7af4265825add1d6ecd44d0699c8d238d (commit)
via f19985bde06da13b2a1396756b2c82300296f8c8 (commit)
via aad22e646509b7393f89b77f59894fba52031b7c (commit)
via 3fe60b931a6529a0196d3bf6374a3569c3e7daba (commit)
via 45f97add7c916d68c1339245bd55643abda71822 (commit)
via 9c08b7c139cb95be8b3fbf21c46c564bfaff8c56 (commit)
via 88a078653a15d157b7c6f3ba57211f11f9121dab (commit)
via 0526c55f20ee7480533567fad62ea00e6bf31786 (commit)
via b5dfaef72b2e2725810320cd2cf703463b3e95e1 (commit)
via aec9ee665b18e5929d9af192e13efb5105c52254 (commit)
via fedd37d9ce70571f305c8c8e66fd9ec7d837585b (commit)
via 87c9a0ce41757c9c0ff2f3f19054edfd8296ef31 (commit)
via 2013c162af618e701ba4caf84fa304ac38c92501 (commit)
via 958ea3a13e8d5ffd23f2540d7a0eb2926667a206 (commit)
via 1e3f649e2b78bf936ac3ca83c0992b0cb9add327 (commit)
via 4a384cb88b02fb086e1035971dd142115cf9b377 (commit)
via abfffeaad949c9f47bb6058ee17e88c7092ffbe6 (commit)
via 0823ded6d3858a4acaaaca57b2a4803a0a5884c8 (commit)
via c9703eb11102d27e22f5f5119db111dd1b41f559 (commit)
via d15788827c453a1103b926672b7cdb1f1a21f487 (commit)
from 6d39a8f70009c43032d96da8ceb8934e6e67a159 (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 -----------------------------------------------------------------
-----------------------------------------------------------------------
Summary of changes:
Makefile | 4 +-
README | 36 --
README.md | 116 ++++
include/libnsgif.h | 194 ------
include/nsgif.h | 346 ++++++++++
src/Makefile | 2 +-
src/gif.c | 1766 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/libnsgif.c | 1461 -------------------------------------------
test/Makefile | 2 +-
test/cli.c | 763 +++++++++++++++++++++++
test/cli.h | 94 +++
test/decode_gif.c | 243 --------
test/nsgif.c | 301 +++++++++
test/runtest.sh | 61 +-
14 files changed, 3421 insertions(+), 1968 deletions(-)
delete mode 100644 README
create mode 100644 README.md
delete mode 100644 include/libnsgif.h
create mode 100644 include/nsgif.h
create mode 100644 src/gif.c
delete mode 100644 src/libnsgif.c
create mode 100644 test/cli.c
create mode 100644 test/cli.h
delete mode 100644 test/decode_gif.c
create mode 100644 test/nsgif.c
diff --git a/Makefile b/Makefile
index c348e20..42aba3a 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ include $(NSSHARED)/makefiles/Makefile.tools
TESTRUNNER = test/runtest.sh $(BUILDDIR) $(EXEEXT)
# Toolchain flags
-WARNFLAGS := -Wall -W -Wundef -Wpointer-arith -Wcast-align \
+WARNFLAGS := -Wall -Wextra -W -Wundef -Wpointer-arith -Wcast-align \
-Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes \
-Wmissing-declarations -Wnested-externs -pedantic
# BeOS/Haiku standard library headers create warnings
@@ -44,6 +44,6 @@ include $(NSBUILD)/Makefile.top
# Extra installation rules
I := /$(INCLUDEDIR)
-INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):include/libnsgif.h
+INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):include/nsgif.h
INSTALL_ITEMS := $(INSTALL_ITEMS) /$(LIBDIR)/pkgconfig:lib$(COMPONENT).pc.in
INSTALL_ITEMS := $(INSTALL_ITEMS) /$(LIBDIR):$(OUTPUT)
diff --git a/README b/README
deleted file mode 100644
index 498ee46..0000000
--- a/README
+++ /dev/null
@@ -1,36 +0,0 @@
-libnsgif - Decoding GIF files
-=============================
-
-The functions provided by this library allow for efficient progressive
-GIF decoding. Whilst the initialisation does not ensure that there is
-sufficient image data to complete the entire frame, it does ensure
-that the information provided is valid. Any subsequent attempts to
-decode an initialised GIF are guaranteed to succeed, and any bytes of
-the image not present are assumed to be totally transparent.
-
-To begin decoding a GIF, the 'gif' structure must be initialised with
-the 'gif_data' and 'buffer_size' set to their initial values. The
-'buffer_position' should initially be 0, and will be internally
-updated as the decoding commences. The caller should then repeatedly
-call gif_initialise() with the structure until the function returns 1,
-or no more data is avaliable.
-
-Once the initialisation has begun, the decoder completes the variables
-'frame_count' and 'frame_count_partial'. The former being the total
-number of frames that have been successfully initialised, and the
-latter being the number of frames that a partial amount of data is
-available for. This assists the caller in managing the animation
-whilst decoding is continuing.
-
-To decode a frame, the caller must use gif_decode_frame() which
-updates the current 'frame_image' to reflect the desired frame. The
-required 'disposal_method' is also updated to reflect how the frame
-should be plotted. The caller must not assume that the current
-'frame_image' will be valid between calls if initialisation is still
-occuring, and should either always request that the frame is decoded
-(no processing will occur if the 'decoded_frame' has not been
-invalidated by initialisation) or perform the check itself.
-
-It should be noted that gif_finalise() should always be called, even
-if no frames were initialised. Additionally, it is the responsibility
-of the caller to free 'gif_data'.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d821ac6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,116 @@
+LibNSGIF: NetSurf GIF decoder
+=============================
+
+LibNSGIF is a C library for decoding GIF format images and animations.
+It is licenced under the MIT licence.
+
+This library aims to provide a simple API for robust decoding of GIF files.
+
+Details
+-------
+
+The GIF source data is scanned prior to decoding, allowing for efficient
+decoding. The scanning phase will scan currently available data and will
+resume from where it left off when called with additional data.
+
+Only one frame is ever fully decoded to a bitmap at a time, reducing memory
+usage for large GIFs.
+
+Using
+-----
+
+LibNSGIF allows the client to allocate the bitmap into which the GIF is
+decoded. The client can have an arbitrary bitmap structure, that is simply
+a void pointer to LibNSGIF. The client must provide a callback table for
+interacting with bitmaps. This table must include as a minimum functions to
+create and destroy bitmaps, and a function to get a pointer to the bitmap's
+pixel data buffer.
+
+To load a GIF, first create an nsgif object with `nsgif_create()`.
+
+```c
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Now you can load the GIF source data into the nsgif object with
+`nsgif_data_scan()`:
+
+```c
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+This scans the source data and decodes information about each frame, however
+it doesn't decode any of the bitmap data for the frames. The client may call
+`nsgif_data_scan()` multiple times as source data is fetched. Once the
+function has returned `NSGIF_OK` it has enough data to display at least one
+frame. The early frames can be decoded before the later frames are scanned.
+Frames have to be scanned before they can be decoded.
+
+To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
+and then call `nsgif_frame_decode()` for each frame, and manage the animation,
+and non-displayable frames yourself, or you can use the helper function,
+`nsgif_frame_prepare()`:
+
+```c
+ err = nsgif_frame_prepare(gif, &area, &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+
+ // Update our bitmap to know it should be showing `frame_new` now.
+ // Trigger redraw of `area` of image.
+
+ if (delay_cs != NSGIF_INFINITE) {
+ // Schedule next frame in delay_cs.
+ }
+```
+
+This will return the number of the next frame to be decoded, the delay in cs
+before the next frame should be decoded, and the area of the bitmap that needs
+to be redrawn.
+
+> **Note**: GIF frames may only occupy a portion of the overall bitmap, and only
+> redrawing the area that has changed may be more efficient than redrawing the
+> whole thing. The returned area comprises both any region that has been
+> changed in the disposal of the previous frame and the new frame.
+
+GIF files can limit the number of animation loops to a finite number or they
+may only have one frame. In either of these cases, the returned delay is
+`NSGIF_INFINITE` indicating that the animation is complete. Subsequent calls
+to `nsgif_frame_prepare()` will return `NSGIF_ERR_ANIMATION_END`.
+
+To force the repeat of an animation, call `nsgif_reset()`.
+
+One reason for the two-step decoding of frames is that it enables deferred
+decoding. You can call `nsgif_frame_prepare()` and cause a redraw of that
+portion of your document. If the GIF is off screen (another tab, or scrolled
+out of sight), there is no need to decode it at all.
+
+Once the bitmap is needed for a redraw, you can decode the correct frame
+on-demand with:
+
+```c
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Note that this will be a no-op if the requested frame already happens to be
+the decoded frame.
+
+Once you are done with the GIF, free up the nsgif object with:
+
+```c
+ nsgif_destroy(gif);
+```
diff --git a/include/libnsgif.h b/include/libnsgif.h
deleted file mode 100644
index b281034..0000000
--- a/include/libnsgif.h
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
- * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-/**
- * \file
- * Interface to progressive animated GIF file decoding.
- */
-
-#ifndef _LIBNSGIF_H_
-#define _LIBNSGIF_H_
-
-#include <stdint.h>
-#include <stdbool.h>
-
-/* Error return values */
-typedef enum {
- GIF_WORKING = 1,
- GIF_OK = 0,
- GIF_INSUFFICIENT_DATA = -1,
- GIF_INSUFFICIENT_FRAME_DATA = GIF_INSUFFICIENT_DATA,
- GIF_FRAME_DATA_ERROR = -2,
- GIF_DATA_ERROR = -4,
- GIF_INSUFFICIENT_MEMORY = -5,
- GIF_FRAME_NO_DISPLAY = -6,
- GIF_END_OF_FRAME = -7
-} gif_result;
-
-/** GIF frame data */
-typedef struct gif_frame {
- /** whether the frame should be displayed/animated */
- bool display;
- /** delay (in cs) before animating the frame */
- uint32_t frame_delay;
-
- /* Internal members are listed below */
-
- /** offset (in bytes) to the GIF frame data */
- uint32_t frame_pointer;
- /** whether the frame has previously been used */
- bool virgin;
- /** whether the frame is totally opaque */
- bool opaque;
- /** whether a full image redraw is required */
- bool redraw_required;
- /** how the previous frame should be disposed; affects plotting */
- uint8_t disposal_method;
- /** whether we acknowledge transparency */
- bool transparency;
- /** the index designating a transparent pixel */
- uint32_t transparency_index;
- /** x co-ordinate of redraw rectangle */
- uint32_t redraw_x;
- /** y co-ordinate of redraw rectangle */
- uint32_t redraw_y;
- /** width of redraw rectangle */
- uint32_t redraw_width;
- /** height of redraw rectangle */
- uint32_t redraw_height;
- /* Frame flags */
- uint32_t flags;
-} gif_frame;
-
-/* API for Bitmap callbacks */
-typedef void* (*gif_bitmap_cb_create)(int width, int height);
-typedef void (*gif_bitmap_cb_destroy)(void *bitmap);
-typedef uint8_t* (*gif_bitmap_cb_get_buffer)(void *bitmap);
-typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
-typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap);
-typedef void (*gif_bitmap_cb_modified)(void *bitmap);
-
-/** Bitmap callbacks function table */
-typedef struct gif_bitmap_callback_vt {
- /** Create a bitmap. */
- gif_bitmap_cb_create bitmap_create;
- /** Free a bitmap. */
- gif_bitmap_cb_destroy bitmap_destroy;
- /** Return a pointer to the pixel data in a bitmap. */
- gif_bitmap_cb_get_buffer bitmap_get_buffer;
-
- /* Members below are optional */
-
- /** Sets whether a bitmap should be plotted opaque. */
- gif_bitmap_cb_set_opaque bitmap_set_opaque;
- /** Tests whether a bitmap has an opaque alpha channel. */
- gif_bitmap_cb_test_opaque bitmap_test_opaque;
- /** The bitmap image has changed, so flush any persistent cache. */
- gif_bitmap_cb_modified bitmap_modified;
-} gif_bitmap_callback_vt;
-
-/** GIF animation data */
-typedef struct gif_animation {
- /** LZW decode context */
- void *lzw_ctx;
- /** callbacks for bitmap functions */
- gif_bitmap_callback_vt bitmap_callbacks;
- /** pointer to GIF data */
- const uint8_t *gif_data;
- /** width of GIF (may increase during decoding) */
- uint32_t width;
- /** height of GIF (may increase during decoding) */
- uint32_t height;
- /** number of frames decoded */
- uint32_t frame_count;
- /** number of frames partially decoded */
- uint32_t frame_count_partial;
- /** decoded frames */
- gif_frame *frames;
- /** current frame decoded to bitmap */
- int decoded_frame;
- /** currently decoded image; stored as bitmap from bitmap_create callback */
- void *frame_image;
- /** number of times to loop animation */
- int loop_count;
-
- /* Internal members are listed below */
-
- /** current index into GIF data */
- uint32_t buffer_position;
- /** total number of bytes of GIF data available */
- uint32_t buffer_size;
- /** current number of frame holders */
- uint32_t frame_holders;
- /** background index */
- uint32_t bg_index;
- /** background colour */
- uint32_t bg_colour;
- /** image aspect ratio (ignored) */
- uint32_t aspect_ratio;
- /** size of colour table (in entries) */
- uint32_t colour_table_size;
- /** whether the GIF has a global colour table */
- bool global_colours;
- /** global colour table */
- uint32_t *global_colour_table;
- /** local colour table */
- uint32_t *local_colour_table;
- /** current colour table */
- uint32_t *colour_table;
-
- /** previous frame for GIF_FRAME_RESTORE */
- void *prev_frame;
- /** previous frame index */
- int prev_index;
- /** previous frame width */
- unsigned prev_width;
- /** previous frame height */
- unsigned prev_height;
-} gif_animation;
-
-/**
- * Initialises necessary gif_animation members.
- */
-void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks);
-
-/**
- * Initialises any workspace held by the animation and attempts to decode
- * any information that hasn't already been decoded.
- * If an error occurs, all previously decoded frames are retained.
- *
- * \return Error return value.
- * - GIF_FRAME_DATA_ERROR for GIF frame data error
- * - GIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - GIF_INSUFFICIENT_MEMORY for memory error
- * - GIF_DATA_ERROR for GIF error
- * - GIF_OK for successful decoding
- * - GIF_WORKING for successful decoding if more frames are expected
- */
-gif_result gif_initialise(gif_animation *gif, size_t size, const uint8_t *data);
-
-/**
- * Decodes a GIF frame.
- *
- * \return Error return value.
- * - GIF_FRAME_DATA_ERROR for GIF frame data error
- * - GIF_DATA_ERROR for GIF error (invalid frame header)
- * - GIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - GIF_OK for successful decoding
- */
-gif_result gif_decode_frame(gif_animation *gif, uint32_t frame);
-
-/**
- * Releases any workspace held by a gif
- */
-void gif_finalise(gif_animation *gif);
-
-#endif
diff --git a/include/nsgif.h b/include/nsgif.h
new file mode 100644
index 0000000..b60747f
--- /dev/null
+++ b/include/nsgif.h
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
+ * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+/**
+ * \file
+ * Interface to progressive animated GIF file decoding.
+ */
+
+#ifndef NSNSGIF_H
+#define NSNSGIF_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+/** Representation of infinity. */
+#define NSGIF_INFINITE (UINT32_MAX)
+
+typedef struct nsgif nsgif_t;
+
+/**
+ * GIF rectangle structure.
+ *
+ * * Top left coordinate is `(x0, y0)`.
+ * * Width is `x1 - x0`.
+ * * Height is `y1 - y0`.
+ * * Units are pixels.
+ */
+typedef struct nsgif_rect {
+ /** x co-ordinate of redraw rectangle, left */
+ uint32_t x0;
+ /** y co-ordinate of redraw rectangle, top */
+ uint32_t y0;
+ /** x co-ordinate of redraw rectangle, right */
+ uint32_t x1;
+ /** y co-ordinate of redraw rectangle, bottom */
+ uint32_t y1;
+} nsgif_rect_t;
+
+/**
+ * NSGIF return codes.
+ */
+typedef enum {
+ /**
+ * Success.
+ */
+ NSGIF_OK,
+
+ /**
+ * Out of memory error.
+ */
+ NSGIF_ERR_OOM,
+
+ /**
+ * GIF source data is invalid, and no frames are recoverable.
+ */
+ NSGIF_ERR_DATA,
+
+ /**
+ * Frame number is not valid.
+ */
+ NSGIF_ERR_BAD_FRAME,
+
+ /**
+ * GIF source data contained an error in a frame.
+ */
+ NSGIF_ERR_DATA_FRAME,
+
+ /**
+ * Too many frames.
+ */
+ NSGIF_ERR_FRAME_COUNT,
+
+ /**
+ * GIF source data ended without one complete frame available.
+ */
+ NSGIF_ERR_END_OF_DATA,
+
+ /**
+ * GIF source data ended with incomplete frame.
+ */
+ NSGIF_ERR_END_OF_FRAME,
+
+ /**
+ * The current frame cannot be displayed.
+ */
+ NSGIF_ERR_FRAME_DISPLAY,
+
+ /**
+ * Indicates an animation is complete, and \ref nsgif_reset must be
+ * called to restart the animation from the beginning.
+ */
+ NSGIF_ERR_ANIMATION_END,
+} nsgif_error;
+
+/**
+ * Client bitmap type.
+ *
+ * These are client-created and destroyed, via the \ref bitmap callbacks,
+ * but they are owned by a \ref nsgif.
+ */
+typedef void nsgif_bitmap_t;
+
+/** Bitmap callbacks function table */
+typedef struct nsgif_bitmap_cb_vt {
+ /**
+ * Callback to create a bitmap with the given dimensions.
+ *
+ * \param[in] width Required bitmap width in pixels.
+ * \param[in] height Required bitmap height in pixels.
+ * \return pointer to client's bitmap structure or NULL on error.
+ */
+ nsgif_bitmap_t* (*create)(int width, int height);
+
+ /**
+ * Callback to free a bitmap.
+ *
+ * \param[in] bitmap The bitmap to destroy.
+ */
+ void (*destroy)(nsgif_bitmap_t *bitmap);
+
+ /**
+ * Get pointer to pixel buffer in a bitmap.
+ *
+ * The pixel buffer must be `width * height * sizeof(uint32_t)`.
+ *
+ * \param[in] bitmap The bitmap.
+ * \return pointer to bitmap's pixel buffer.
+ */
+ uint8_t* (*get_buffer)(nsgif_bitmap_t *bitmap);
+
+ /* The following functions are optional. */
+
+ /**
+ * Set whether a bitmap can be plotted opaque.
+ *
+ * \param[in] bitmap The bitmap.
+ * \param[in] opaque Whether the current frame is opaque.
+ */
+ void (*set_opaque)(nsgif_bitmap_t *bitmap, bool opaque);
+
+ /**
+ * Tests whether a bitmap has an opaque alpha channel.
+ *
+ * \param[in] bitmap The bitmap.
+ * \return true if the bitmap is opaque, false otherwise.
+ */
+ bool (*test_opaque)(nsgif_bitmap_t *bitmap);
+
+ /**
+ * Bitmap modified notification.
+ *
+ * \param[in] bitmap The bitmap.
+ */
+ void (*modified)(nsgif_bitmap_t *bitmap);
+} nsgif_bitmap_cb_vt;
+
+/**
+ * Convert an error code to a string.
+ *
+ * \param[in] err The error code to convert.
+ * \return String representation of given error code.
+ */
+const char *nsgif_strerror(nsgif_error err);
+
+/**
+ * Create the NSGIF object.
+ *
+ * \param[in] bitmap_vt Bitmap operation functions v-table.
+ * \param[out] gif_out Return NSGIF object on success.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_create(
+ const nsgif_bitmap_cb_vt *bitmap_vt,
+ nsgif_t **gif_out);
+
+/**
+ * Free a NSGIF object.
+ *
+ * \param[in] gif The NSGIF to free.
+ */
+void nsgif_destroy(nsgif_t *gif);
+
+/**
+ * Scan the source image data.
+ *
+ * This is used to feed the source data into LibNSGIF. This must be called
+ * before calling \ref nsgif_frame_decode.
+ *
+ * It can be called multiple times with, with increasing sizes. If it is called
+ * several times, as more data is available (e.g. slow network fetch) the data
+ * already given to \ref nsgif_data_scan must be provided each time.
+ *
+ * For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then
+ * fetch another 10 bytes, you would need to call \ref nsgif_data with a size of
+ * 35 bytes, and the whole 35 bytes must be contiguous memory. It is safe to
+ * `realloc` the source buffer between calls to \ref nsgif_data_scan. (The
+ * actual data pointer is allowed to be different.)
+ *
+ * If an error occurs, all previously scanned frames are retained.
+ *
+ * \param[in] gif The NSGIF object.
+ * \param[in] size Number of bytes in data.
+ * \param[in] data Raw source GIF data.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_data_scan(
+ nsgif_t *gif,
+ size_t size,
+ const uint8_t *data);
+
+/**
+ * Prepare to show a frame.
+ *
+ * If this is the last frame of an animation with a finite loop count, the
+ * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
+ * should be shown forever.
+ *
+ * \param[in] gif The NSGIF object.
+ * \param[out] area The area in pixels that must be redrawn.
+ * \param[out] delay_cs Time to wait after frame_new before next frame in cs.
+ * \param[out] frame_new The frame to decode.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_frame_prepare(
+ nsgif_t *gif,
+ nsgif_rect_t *area,
+ uint32_t *delay_cs,
+ uint32_t *frame_new);
+
+/**
+ * Decodes a GIF frame.
+ *
+ * \param[in] gif The nsgif object.
+ * \param[in] frame The frame number to decode.
+ * \param[out] bitmap On success, returns pointer to the client-allocated,
+ * nsgif-owned client bitmap structure.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_frame_decode(
+ nsgif_t *gif,
+ uint32_t frame,
+ nsgif_bitmap_t **bitmap);
+
+/**
+ * Reset a GIF animation.
+ *
+ * Some animations are only meant to loop N times, and then show the
+ * final frame forever. This function resets the loop and frame counters,
+ * so that the animation can be replayed without the overhead of recreating
+ * the NSGIF object and rescanning the raw data.
+ *
+ * \param[in] gif A NSGIF object.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_reset(
+ nsgif_t *gif);
+
+/**
+ * Information about a GIF.
+ */
+typedef struct nsgif_info {
+ /** width of GIF (may increase during decoding) */
+ uint32_t width;
+ /** height of GIF (may increase during decoding) */
+ uint32_t height;
+ /** number of frames decoded */
+ uint32_t frame_count;
+ /** number of times to loop animation */
+ int loop_max;
+ /** number of animation loops so far */
+ int loop_count;
+} nsgif_info_t;
+
+/**
+ * Frame disposal method.
+ *
+ * Clients do not need to know about this, it is provided purely for dumping
+ * raw information about GIF frames.
+ */
+enum nsgif_disposal {
+ NSGIF_DISPOSAL_UNSPECIFIED, /**< No disposal method specified. */
+ NSGIF_DISPOSAL_NONE, /**< Frame remains. */
+ NSGIF_DISPOSAL_RESTORE_BG, /**< Clear frame to background colour. */
+ NSGIF_DISPOSAL_RESTORE_PREV, /**< Restore previous frame. */
+ NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
+};
+
+/**
+ * Convert a disposal method to a string.
+ *
+ * \param[in] disposal The disposal method to convert.
+ * \return String representation of given disposal method.
+ */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal);
+
+/**
+ * Information about a GIF frame.
+ */
+typedef struct nsgif_frame_info {
+ /** whether the frame should be displayed/animated */
+ bool display;
+
+ /** Disposal method for previous frame; affects plotting */
+ uint8_t disposal;
+ /** delay (in cs) before animating the frame */
+ uint32_t delay;
+
+ /** Frame's redraw rectangle. */
+ nsgif_rect_t rect;
+} nsgif_frame_info_t;
+
+/**
+ * Get information about a GIF from an NSGIF object.
+ *
+ * \param[in] gif The NSGIF object to get info for.
+ *
+ * \return The gif info, or NULL on error.
+ */
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
+
+/**
+ * Get information about a GIF from an NSGIF object.
+ *
+ * \param[in] gif The NSGIF object to get frame info for.
+ * \param[in] frame The frame number to get info for.
+ *
+ * \return The gif frame info, or NULL on error.
+ */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif_t *gif,
+ uint32_t frame);
+
+#endif
diff --git a/src/Makefile b/src/Makefile
index cb5d31f..e1e1fa7 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,4 +1,4 @@
# Sources
-DIR_SOURCES := libnsgif.c lzw.c
+DIR_SOURCES := gif.c lzw.c
include $(NSBUILD)/Makefile.subdir
diff --git a/src/gif.c b/src/gif.c
new file mode 100644
index 0000000..467ff90
--- /dev/null
+++ b/src/gif.c
@@ -0,0 +1,1766 @@
+/*
+ * Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
+ * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "lzw.h"
+#include "nsgif.h"
+
+/** Maximum colour table size */
+#define NSGIF_MAX_COLOURS 256
+
+/** GIF frame data */
+typedef struct nsgif_frame {
+ struct nsgif_frame_info info;
+
+ /** offset (in bytes) to the GIF frame data */
+ uint32_t frame_pointer;
+ /** whether the frame has previously been decoded. */
+ bool decoded;
+ /** whether the frame is totally opaque */
+ bool opaque;
+ /** whether a full image redraw is required */
+ bool redraw_required;
+
+ /** whether we acknowledge transparency */
+ bool transparency;
+ /** the index designating a transparent pixel */
+ uint32_t transparency_index;
+
+ /* Frame flags */
+ uint32_t flags;
+} nsgif_frame;
+
+/** GIF animation data */
+struct nsgif {
+ struct nsgif_info info;
+
+ /** LZW decode context */
+ void *lzw_ctx;
+ /** callbacks for bitmap functions */
+ nsgif_bitmap_cb_vt bitmap;
+ /** decoded frames */
+ nsgif_frame *frames;
+ /** current frame */
+ uint32_t frame;
+ /** current frame decoded to bitmap */
+ uint32_t decoded_frame;
+ /** currently decoded image; stored as bitmap from bitmap_create callback */
+ nsgif_bitmap_t *frame_image;
+
+ uint16_t delay_min;
+ uint16_t delay_default;
+
+ /** number of frames partially decoded */
+ uint32_t frame_count_partial;
+
+ /** pointer to GIF data */
+ const uint8_t *buf;
+ /** current index into GIF data */
+ uint32_t buf_pos;
+ /** total number of bytes of GIF data available */
+ uint32_t buf_len;
+
+ /** current number of frame holders */
+ uint32_t frame_holders;
+ /** background index */
+ uint32_t bg_index;
+ /** background colour */
+ uint32_t bg_colour;
+ /** image aspect ratio (ignored) */
+ uint32_t aspect_ratio;
+ /** size of colour table (in entries) */
+ uint32_t colour_table_size;
+
+ /** whether the GIF has a global colour table */
+ bool global_colours;
+ /** current colour table */
+ uint32_t *colour_table;
+ /** global colour table */
+ uint32_t global_colour_table[NSGIF_MAX_COLOURS];
+ /** local colour table */
+ uint32_t local_colour_table[NSGIF_MAX_COLOURS];
+
+ /** previous frame for NSGIF_FRAME_RESTORE */
+ void *prev_frame;
+ /** previous frame index */
+ uint32_t prev_index;
+};
+
+/**
+ * Helper macro to get number of elements in an array.
+ *
+ * \param[in] _a Array to count elements of.
+ * \return NUlber of elements in array.
+ */
+#define NSGIF_ARRAY_LEN(_a) ((sizeof(_a)) / (sizeof(*_a)))
+
+/**
+ *
+ * \file
+ * \brief GIF image decoder
+ *
+ * The GIF format is thoroughly documented; a full description can be found at
+ * http://www.w3.org/Graphics/GIF/spec-gif89a.txt
+ *
+ * \todo Plain text and comment extensions should be implemented.
+ */
+
+/** Internal flag that the colour table needs to be processed */
+#define NSGIF_PROCESS_COLOURS 0xaa000000
+
+/** Internal flag that a frame is invalid/unprocessed */
+#define NSGIF_FRAME_INVALID UINT32_MAX
+
+/** Transparent colour */
+#define NSGIF_TRANSPARENT_COLOUR 0x00
+
+/** No transparency */
+#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu)
+
+/* GIF Flags */
+#define NSGIF_COLOUR_TABLE_MASK 0x80
+#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07
+#define NSGIF_BLOCK_TERMINATOR 0x00
+#define NSGIF_TRAILER 0x3b
+
+/**
+ * Convert an LZW result code to equivalent GIF result code.
+ *
+ * \param[in] l_res LZW response code.
+ * \return GIF result code.
+ */
+static nsgif_error nsgif__error_from_lzw(lzw_result l_res)
+{
+ static const nsgif_error g_res[] = {
+ [LZW_OK] = NSGIF_OK,
+ [LZW_NO_MEM] = NSGIF_ERR_OOM,
+ [LZW_OK_EOD] = NSGIF_ERR_END_OF_DATA,
+ [LZW_NO_DATA] = NSGIF_ERR_END_OF_DATA,
+ [LZW_EOI_CODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_ICODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_CODE] = NSGIF_ERR_DATA_FRAME,
+ };
+ assert(l_res != LZW_BAD_PARAM);
+ assert(l_res != LZW_NO_COLOUR);
+ return g_res[l_res];
+}
+
+/**
+ * Updates the sprite memory size
+ *
+ * \param gif The animation context
+ * \param width The width of the sprite
+ * \param height The height of the sprite
+ * \return NSGIF_ERR_OOM for a memory error NSGIF_OK for success
+ */
+static nsgif_error nsgif__initialise_sprite(
+ struct nsgif *gif,
+ uint32_t width,
+ uint32_t height)
+{
+ /* Already allocated? */
+ if (gif->frame_image) {
+ return NSGIF_OK;
+ }
+
+ assert(gif->bitmap.create);
+ gif->frame_image = gif->bitmap.create(width, height);
+ if (gif->frame_image == NULL) {
+ return NSGIF_ERR_OOM;
+ }
+
+ return NSGIF_OK;
+}
+
+/**
+ * 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* nsgif__bitmap_get(
+ struct nsgif *gif)
+{
+ nsgif_error ret;
+
+ /* Make sure we have a buffer to decode to. */
+ ret = nsgif__initialise_sprite(gif, gif->info.width, gif->info.height);
+ if (ret != NSGIF_OK) {
+ return NULL;
+ }
+
+ /* Get the frame data */
+ assert(gif->bitmap.get_buffer);
+ return (uint32_t *)gif->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 nsgif__bitmap_modified(
+ const struct nsgif *gif)
+{
+ if (gif->bitmap.modified) {
+ gif->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] frame The frame that has been decoded.
+ */
+static inline void nsgif__bitmap_set_opaque(
+ const struct nsgif *gif,
+ const struct nsgif_frame *frame)
+{
+ if (gif->bitmap.set_opaque) {
+ gif->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 nsgif__bitmap_get_opaque(
+ const struct nsgif *gif)
+{
+ if (gif->bitmap.test_opaque) {
+ return gif->bitmap.test_opaque(
+ gif->frame_image);
+ }
+
+ return false;
+}
+
+static void nsgif__record_frame(
+ struct nsgif *gif,
+ const uint32_t *bitmap)
+{
+ uint32_t *prev_frame;
+
+ if (gif->decoded_frame == NSGIF_FRAME_INVALID ||
+ gif->decoded_frame == gif->prev_index) {
+ /* No frame to copy, or already have this frame recorded. */
+ return;
+ }
+
+ bitmap = nsgif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return;
+ }
+
+ if (gif->prev_frame == NULL) {
+ prev_frame = realloc(gif->prev_frame,
+ gif->info.width * gif->info.height * 4);
+ if (prev_frame == NULL) {
+ return;
+ }
+ } else {
+ prev_frame = gif->prev_frame;
+ }
+
+ memcpy(prev_frame, bitmap, gif->info.width * gif->info.height * 4);
+
+ gif->prev_frame = prev_frame;
+ gif->prev_index = gif->decoded_frame;
+}
+
+static nsgif_error nsgif__recover_frame(
+ const struct nsgif *gif,
+ uint32_t *bitmap)
+{
+ const uint32_t *prev_frame = gif->prev_frame;
+ unsigned height = gif->info.height;
+ unsigned width = gif->info.width;
+
+ memcpy(bitmap, prev_frame, height * width * sizeof(*bitmap));
+
+ return NSGIF_OK;
+}
+
+/**
+ * Get the next line for GIF decode.
+ *
+ * Note that the step size must be initialised to 24 at the start of the frame
+ * (when y == 0). This is because of the first two passes of the frame have
+ * the same step size of 8, and the step size is used to determine the current
+ * pass.
+ *
+ * \param[in] height Frame height in pixels.
+ * \param[in,out] y Current row, starting from 0, updated on exit.
+ * \param[in,out] step Current step starting with 24, updated on exit.
+ * \return true if there is a row to process, false at the end of the frame.
+ */
+static inline bool nsgif__deinterlace(uint32_t height, uint32_t *y, uint8_t *step)
+{
+ *y += *step & 0xf;
+
+ if (*y < height) return true;
+
+ switch (*step) {
+ case 24: *y = 4; *step = 8; if (*y < height) return true;
+ /* Fall through. */
+ case 8: *y = 2; *step = 4; if (*y < height) return true;
+ /* Fall through. */
+ case 4: *y = 1; *step = 2; if (*y < height) return true;
+ /* Fall through. */
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Get the next line for GIF decode.
+ *
+ * \param[in] interlace Non-zero if the frame is not interlaced.
+ * \param[in] height Frame height in pixels.
+ * \param[in,out] y Current row, starting from 0, updated on exit.
+ * \param[in,out] step Current step starting with 24, updated on exit.
+ * \return true if there is a row to process, false at the end of the frame.
+ */
+static inline bool nsgif__next_row(uint32_t interlace,
+ uint32_t height, uint32_t *y, uint8_t *step)
+{
+ if (!interlace) {
+ return (++*y != height);
+ } else {
+ return nsgif__deinterlace(height, y, step);
+ }
+}
+
+/**
+ * Get any frame clip adjustment for the image extent.
+ *
+ * \param[in] frame_off Frame's X or Y offset.
+ * \param[in] frame_dim Frame width or height.
+ * \param[in] image_ext Image width or height constraint.
+ * \return the amount the frame needs to be clipped to fit the image in given
+ * dimension.
+ */
+static inline uint32_t gif__clip(
+ uint32_t frame_off,
+ uint32_t frame_dim,
+ uint32_t image_ext)
+{
+ uint32_t frame_ext = frame_off + frame_dim;
+
+ if (frame_ext <= image_ext) {
+ return 0;
+ }
+
+ return frame_ext - image_ext;
+}
+
+/**
+ * Perform any jump over decoded data, to accommodate clipped portion of frame.
+ *
+ * \param[in,out] skip Number of pixels of data to jump.
+ * \param[in,out] available Number of pixels of data currently available.
+ * \param[in,out] pos Position in decoded pixel value data.
+ */
+static inline void gif__jump_data(
+ uint32_t *skip,
+ uint32_t *available,
+ const uint8_t **pos)
+{
+ uint32_t jump = (*skip < *available) ? *skip : *available;
+
+ *skip -= jump;
+ *available -= jump;
+ *pos += jump;
+}
+
+static nsgif_error nsgif__decode_complex(
+ struct nsgif *gif,
+ uint32_t width,
+ uint32_t height,
+ uint32_t offset_x,
+ uint32_t offset_y,
+ uint32_t interlace,
+ const uint8_t *data,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
+{
+ lzw_result res;
+ nsgif_error ret = NSGIF_OK;
+ uint32_t clip_x = gif__clip(offset_x, width, gif->info.width);
+ uint32_t clip_y = gif__clip(offset_y, height, gif->info.height);
+ const uint8_t *uncompressed;
+ uint32_t available = 0;
+ uint8_t step = 24;
+ uint32_t skip = 0;
+ uint32_t y = 0;
+
+ if (offset_x >= gif->info.width ||
+ offset_y >= gif->info.height) {
+ return NSGIF_OK;
+ }
+
+ width -= clip_x;
+ height -= clip_y;
+
+ if (width == 0 || height == 0) {
+ return NSGIF_OK;
+ }
+
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init(gif->lzw_ctx, data[0],
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
+ if (res != LZW_OK) {
+ return nsgif__error_from_lzw(res);
+ }
+
+ do {
+ uint32_t x;
+ uint32_t *frame_scanline;
+
+ frame_scanline = frame_data + offset_x +
+ (y + offset_y) * gif->info.width;
+
+ x = width;
+ while (x > 0) {
+ unsigned row_available;
+ while (available == 0) {
+ if (res != LZW_OK) {
+ /* Unexpected end of frame, try to recover */
+ if (res == LZW_OK_EOD) {
+ ret = NSGIF_OK;
+ } else {
+ ret = nsgif__error_from_lzw(res);
+ }
+ return ret;
+ }
+ res = lzw_decode(gif->lzw_ctx,
+ &uncompressed, &available);
+
+ if (available == 0) {
+ return NSGIF_OK;
+ }
+ gif__jump_data(&skip, &available, &uncompressed);
+ }
+
+ 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++;
+ }
+ }
+ }
+
+ skip = clip_x;
+ gif__jump_data(&skip, &available, &uncompressed);
+ } while (nsgif__next_row(interlace, height, &y, &step));
+
+ return ret;
+}
+
+static nsgif_error nsgif__decode_simple(
+ struct nsgif *gif,
+ uint32_t height,
+ uint32_t offset_y,
+ const uint8_t *data,
+ uint32_t transparency_index,
+ uint32_t *restrict frame_data,
+ uint32_t *restrict colour_table)
+{
+ uint32_t pixels = gif->info.width * height;
+ uint32_t written = 0;
+ nsgif_error ret = NSGIF_OK;
+ lzw_result res;
+
+ if (offset_y >= gif->info.height) {
+ return NSGIF_OK;
+ }
+
+ height -= gif__clip(offset_y, height, gif->info.height);
+
+ if (height == 0) {
+ return NSGIF_OK;
+ }
+
+ /* Initialise the LZW decoding */
+ res = lzw_decode_init_map(gif->lzw_ctx, data[0],
+ transparency_index, colour_table,
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
+ if (res != LZW_OK) {
+ return nsgif__error_from_lzw(res);
+ }
+
+ frame_data += (offset_y * gif->info.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 = NSGIF_OK;
+ } else {
+ ret = nsgif__error_from_lzw(res);
+ }
+ break;
+ }
+ }
+
+ if (pixels == 0) {
+ ret = NSGIF_OK;
+ }
+
+ return ret;
+}
+
+static inline nsgif_error nsgif__decode(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t *data,
+ uint32_t *restrict frame_data)
+{
+ enum {
+ GIF_MASK_INTERLACE = 0x40,
+ };
+
+ nsgif_error ret;
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
+ uint32_t interlace = frame->flags & GIF_MASK_INTERLACE;
+ uint32_t transparency_index = frame->transparency_index;
+ uint32_t *restrict colour_table = gif->colour_table;
+
+ if (interlace == false && width == gif->info.width && offset_x == 0) {
+ ret = nsgif__decode_simple(gif, height, offset_y,
+ data, transparency_index,
+ frame_data, colour_table);
+ } else {
+ ret = nsgif__decode_complex(gif, width, height,
+ offset_x, offset_y, interlace,
+ data, transparency_index,
+ frame_data, colour_table);
+ }
+
+ return ret;
+}
+
+/**
+ * Restore a GIF to the background colour.
+ *
+ * \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 nsgif__restore_bg(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ uint32_t *bitmap)
+{
+ if (frame == NULL) {
+ memset(bitmap, NSGIF_TRANSPARENT_COLOUR,
+ gif->info.width * gif->info.height * sizeof(*bitmap));
+ } else {
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
+
+ width -= gif__clip(offset_x, width, gif->info.width);
+ height -= gif__clip(offset_y, height, gif->info.height);
+
+ if (frame->info.display == false || width == 0) {
+ return;
+ }
+
+ if (frame->transparency) {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->info.width;
+ memset(scanline, NSGIF_TRANSPARENT_COLOUR,
+ width * sizeof(*bitmap));
+ }
+ } else {
+ for (uint32_t y = 0; y < height; y++) {
+ uint32_t *scanline = bitmap + offset_x +
+ (offset_y + y) * gif->info.width;
+ for (uint32_t x = 0; x < width; x++) {
+ scanline[x] = gif->bg_colour;
+ }
+ }
+ }
+ }
+}
+
+static nsgif_error nsgif__update_bitmap(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t *data,
+ uint32_t frame_idx)
+{
+ nsgif_error ret;
+ uint32_t *bitmap;
+
+ gif->decoded_frame = frame_idx;
+
+ bitmap = nsgif__bitmap_get(gif);
+ if (bitmap == NULL) {
+ return NSGIF_ERR_OOM;
+ }
+
+ /* Handle any bitmap clearing/restoration required before decoding this
+ * frame. */
+ if (frame_idx == 0 || gif->decoded_frame == NSGIF_FRAME_INVALID) {
+ nsgif__restore_bg(gif, NULL, bitmap);
+
+ } else {
+ struct nsgif_frame *prev = &gif->frames[frame_idx - 1];
+
+ if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_BG) {
+ nsgif__restore_bg(gif, prev, bitmap);
+
+ } else if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
+ ret = nsgif__recover_frame(gif, bitmap);
+ if (ret != NSGIF_OK) {
+ nsgif__restore_bg(gif, prev, bitmap);
+ }
+ }
+ }
+
+ if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
+ /* Store the previous frame for later restoration */
+ nsgif__record_frame(gif, bitmap);
+ }
+
+ ret = nsgif__decode(gif, frame, data, bitmap);
+
+ nsgif__bitmap_modified(gif);
+
+ if (!frame->decoded) {
+ frame->opaque = nsgif__bitmap_get_opaque(gif);
+ frame->decoded = true;
+ }
+ nsgif__bitmap_set_opaque(gif, frame);
+
+ return ret;
+}
+
+/**
+ * Parse the graphic control extension
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] frame The gif object we're decoding.
+ * \param[in] data The data to decode.
+ * \param[in] len Byte length of data.
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
+ * NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_extension_graphic_control(
+ const struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t *data,
+ size_t len)
+{
+ enum {
+ GIF_MASK_TRANSPARENCY = 0x01,
+ GIF_MASK_DISPOSAL = 0x1c,
+ };
+
+ /* 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 NSGIF_ERR_END_OF_DATA;
+ }
+
+ frame->info.delay = data[3] | (data[4] << 8);
+ if (frame->info.delay < gif->delay_min) {
+ frame->info.delay = gif->delay_default;
+ }
+
+ if (data[2] & GIF_MASK_TRANSPARENCY) {
+ frame->transparency = true;
+ frame->transparency_index = data[5];
+ }
+
+ frame->info.disposal = ((data[2] & GIF_MASK_DISPOSAL) >> 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->info.disposal == NSGIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->info.disposal = NSGIF_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->info.disposal == NSGIF_DISPOSAL_RESTORE_BG ||
+ frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV;
+
+ return NSGIF_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 NSGIF_ERR_END_OF_DATA if more data is needed,
+ * NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_extension_application(
+ struct nsgif *gif,
+ const 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 NSGIF_ERR_END_OF_DATA;
+ }
+
+ if ((data[1] == 0x0b) &&
+ (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) &&
+ (data[13] == 0x03) && (data[14] == 0x01)) {
+ gif->info.loop_max = data[15] | (data[16] << 8);
+ }
+
+ return NSGIF_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] pos Current position in data, updated on exit.
+ * \param[in] decode Whether to decode or skip over the extension.
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
+ * NSGIF_OK for success.
+ */
+static nsgif_error nsgif__parse_frame_extensions(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t **pos,
+ bool decode)
+{
+ enum {
+ GIF_EXT_INTRODUCER = 0x21,
+ GIF_EXT_GRAPHIC_CONTROL = 0xf9,
+ GIF_EXT_COMMENT = 0xfe,
+ GIF_EXT_PLAIN_TEXT = 0x01,
+ GIF_EXT_APPLICATION = 0xff,
+ };
+ const uint8_t *nsgif_data = *pos;
+ const uint8_t *nsgif_end = gif->buf + gif->buf_len;
+ int nsgif_bytes = nsgif_end - nsgif_data;
+
+ /* Initialise the extensions */
+ while (nsgif_bytes > 0 && nsgif_data[0] == GIF_EXT_INTRODUCER) {
+ bool block_step = true;
+ nsgif_error ret;
+
+ nsgif_data++;
+ nsgif_bytes--;
+
+ if (nsgif_bytes == 0) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ /* Switch on extension label */
+ switch (nsgif_data[0]) {
+ case GIF_EXT_GRAPHIC_CONTROL:
+ if (decode) {
+ ret = nsgif__parse_extension_graphic_control(
+ gif, frame,
+ nsgif_data,
+ nsgif_bytes);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+ }
+ break;
+
+ case GIF_EXT_APPLICATION:
+ if (decode) {
+ ret = nsgif__parse_extension_application(
+ gif, nsgif_data, nsgif_bytes);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+ }
+ break;
+
+ case GIF_EXT_COMMENT:
+ /* Move the pointer to the first data sub-block Skip 1
+ * byte for the extension label. */
+ ++nsgif_data;
+ block_step = false;
+ break;
+
+ 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 (nsgif_bytes < 2) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+ nsgif_data += 2 + nsgif_data[1];
+ }
+
+ /* Repeatedly skip blocks until we get a zero block or run out
+ * of data. This data is ignored by this gif decoder. */
+ while (nsgif_data < nsgif_end && nsgif_data[0] != NSGIF_BLOCK_TERMINATOR) {
+ nsgif_data += nsgif_data[0] + 1;
+ if (nsgif_data >= nsgif_end) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+ }
+ nsgif_data++;
+ nsgif_bytes = nsgif_end - nsgif_data;
+ }
+
+ if (nsgif_data > nsgif_end) {
+ nsgif_data = nsgif_end;
+ }
+
+ /* Set buffer position and return */
+ *pos = nsgif_data;
+ return NSGIF_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.
+ * \param[in] pos Current position in data, updated on exit.
+ * \param[in] decode Whether to decode the image descriptor.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_image_descriptor(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t **pos,
+ bool decode)
+{
+ const uint8_t *data = *pos;
+ size_t len = gif->buf + gif->buf_len - data;
+ enum {
+ NSGIF_IMAGE_DESCRIPTOR_LEN = 10u,
+ NSGIF_IMAGE_SEPARATOR = 0x2Cu,
+ };
+
+ assert(gif != NULL);
+ assert(frame != NULL);
+
+ if (len < NSGIF_IMAGE_DESCRIPTOR_LEN) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ if (decode) {
+ uint32_t x, y, w, h;
+
+ if (data[0] != NSGIF_IMAGE_SEPARATOR) {
+ return NSGIF_ERR_DATA_FRAME;
+ }
+
+ 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->info.rect.x0 = x;
+ frame->info.rect.y0 = y;
+ frame->info.rect.x1 = x + w;
+ frame->info.rect.y1 = y + h;
+
+ /* Allow first frame to grow image dimensions. */
+ if (gif->info.frame_count == 0) {
+ if (x + w > gif->info.width) {
+ gif->info.width = x + w;
+ }
+ if (y + h > gif->info.height) {
+ gif->info.height = y + h;
+ }
+ }
+ }
+
+ *pos += NSGIF_IMAGE_DESCRIPTOR_LEN;
+ return NSGIF_OK;
+}
+
+/**
+ * Extract a GIF colour table into a LibNSGIF colour table buffer.
+ *
+ * \param[in] gif The gif object we're decoding.
+ * \param[in] colour_table The colour table to populate.
+ * \param[in] colour_table_entries The number of colour table entries.
+ * \param[in] pos Current position in data, updated on exit.
+ * \param[in] decode Whether to decode the colour table.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__colour_table_extract(
+ struct nsgif *gif,
+ uint32_t colour_table[NSGIF_MAX_COLOURS],
+ size_t colour_table_entries,
+ const uint8_t **pos,
+ bool decode)
+{
+ const uint8_t *data = *pos;
+ size_t len = gif->buf + gif->buf_len - data;
+
+ if (len < colour_table_entries * 3) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ if (decode) {
+ int count = colour_table_entries;
+ uint8_t *entry = (uint8_t *)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 */
+ }
+ }
+
+ *pos += colour_table_entries * 3;
+ return NSGIF_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.
+ * \param[in] pos Current position in data, updated on exit.
+ * \param[in] decode Whether to decode the colour table.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_colour_table(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t **pos,
+ bool decode)
+{
+ nsgif_error ret;
+
+ assert(gif != NULL);
+ assert(frame != NULL);
+
+ if ((frame->flags & NSGIF_COLOUR_TABLE_MASK) == 0) {
+ gif->colour_table = gif->global_colour_table;
+ return NSGIF_OK;
+ }
+
+ ret = nsgif__colour_table_extract(gif, gif->local_colour_table,
+ 2 << (frame->flags & NSGIF_COLOUR_TABLE_SIZE_MASK),
+ pos, decode);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ gif->colour_table = gif->local_colour_table;
+ return NSGIF_OK;
+}
+
+/**
+ * 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 parse image data for.
+ * \param[in] pos Current position in data, updated on exit.
+ * \param[in] decode Whether to decode the image data.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_image_data(
+ struct nsgif *gif,
+ struct nsgif_frame *frame,
+ const uint8_t **pos,
+ bool decode)
+{
+ const uint8_t *data = *pos;
+ size_t len = gif->buf + gif->buf_len - data;
+ uint32_t frame_idx = frame - gif->frames;
+ uint8_t minimum_code_size;
+ nsgif_error 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) {
+ default: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
+ break;
+ case 2: if (data[1] == NSGIF_TRAILER) return NSGIF_OK;
+ /* Fall through. */
+ case 1: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
+ /* Fall through. */
+ case 0: return NSGIF_ERR_END_OF_DATA;
+ }
+
+ minimum_code_size = data[0];
+ if (minimum_code_size >= LZW_CODE_MAX) {
+ return NSGIF_ERR_DATA_FRAME;
+ }
+
+ if (decode) {
+ ret = nsgif__update_bitmap(gif, frame, data, frame_idx);
+ } else {
+ uint32_t block_size = 0;
+
+ /* Skip the minimum code size. */
+ data++;
+ len--;
+
+ while (block_size != 1) {
+ if (len < 1) return NSGIF_ERR_END_OF_DATA;
+ block_size = data[0] + 1;
+ /* Check if the frame data runs off the end of the file */
+ if (block_size > len) {
+ block_size = len;
+ return NSGIF_OK;
+ }
+
+ len -= block_size;
+ data += block_size;
+ }
+
+ *pos = data;
+
+ gif->info.frame_count = frame_idx + 1;
+ gif->frames[frame_idx].info.display = true;
+
+ return NSGIF_OK;
+ }
+
+ return ret;
+}
+
+static struct nsgif_frame *nsgif__get_frame(
+ struct nsgif *gif,
+ uint32_t frame_idx)
+{
+ struct nsgif_frame *frame;
+
+ if (gif->frame_holders > frame_idx) {
+ frame = &gif->frames[frame_idx];
+ } else {
+ /* Allocate more memory */
+ size_t count = frame_idx + 1;
+ struct nsgif_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 = NSGIF_NO_TRANSPARENCY;
+ frame->frame_pointer = gif->buf_pos;
+ frame->redraw_required = false;
+ frame->info.display = false;
+ frame->info.disposal = 0;
+ frame->info.delay = 10;
+ frame->decoded = false;
+ }
+
+ return frame;
+}
+
+/**
+ * Attempts to initialise the next frame
+ *
+ * \param[in] gif The animation context
+ * \param[in] frame_idx The frame number to decode.
+ * \param[in] decode Whether to decode the graphical image data.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+*/
+static nsgif_error nsgif__process_frame(
+ struct nsgif *gif,
+ uint32_t frame_idx,
+ bool decode)
+{
+ nsgif_error ret;
+ const uint8_t *pos;
+ const uint8_t *end;
+ struct nsgif_frame *frame;
+
+ frame = nsgif__get_frame(gif, frame_idx);
+ if (frame == NULL) {
+ return NSGIF_ERR_OOM;
+ }
+
+ end = gif->buf + gif->buf_len;
+
+ if (decode) {
+ pos = gif->buf + frame->frame_pointer;
+
+ /* Ensure this frame is supposed to be decoded */
+ if (frame->info.display == false) {
+ return NSGIF_OK;
+ }
+
+ /* Ensure the frame is in range to decode */
+ if (frame_idx > gif->frame_count_partial) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ /* Done if frame is already decoded */
+ if (frame_idx == gif->decoded_frame) {
+ return NSGIF_OK;
+ }
+ } else {
+ pos = (uint8_t *)(gif->buf + gif->buf_pos);
+
+ /* Check if we've finished */
+ if (pos < end && pos[0] == NSGIF_TRAILER) {
+ return NSGIF_OK;
+ }
+
+ /* 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 NSGIF_ERR_FRAME_COUNT;
+ }
+ }
+
+ ret = nsgif__parse_frame_extensions(gif, frame, &pos, !decode);
+ if (ret != NSGIF_OK) {
+ goto cleanup;
+ }
+
+ ret = nsgif__parse_image_descriptor(gif, frame, &pos, !decode);
+ if (ret != NSGIF_OK) {
+ goto cleanup;
+ }
+
+ ret = nsgif__parse_colour_table(gif, frame, &pos, decode);
+ if (ret != NSGIF_OK) {
+ goto cleanup;
+ }
+
+ ret = nsgif__parse_image_data(gif, frame, &pos, decode);
+ if (ret != NSGIF_OK) {
+ goto cleanup;
+ }
+
+cleanup:
+ if (!decode) {
+ gif->buf_pos = pos - gif->buf;
+ }
+
+ return ret;
+}
+
+/* exported function documented in nsgif.h */
+void nsgif_destroy(nsgif_t *gif)
+{
+ if (gif == NULL) {
+ return;
+ }
+
+ /* Release all our memory blocks */
+ if (gif->frame_image) {
+ assert(gif->bitmap.destroy);
+ gif->bitmap.destroy(gif->frame_image);
+ gif->frame_image = NULL;
+ }
+
+ free(gif->frames);
+ gif->frames = NULL;
+
+ free(gif->prev_frame);
+ gif->prev_frame = NULL;
+
+ lzw_context_destroy(gif->lzw_ctx);
+ gif->lzw_ctx = NULL;
+
+ free(gif);
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
+{
+ nsgif_t *gif;
+
+ gif = calloc(1, sizeof(*gif));
+ if (gif == NULL) {
+ return NSGIF_ERR_OOM;
+ }
+
+ gif->bitmap = *bitmap_vt;
+ gif->decoded_frame = NSGIF_FRAME_INVALID;
+ gif->prev_index = NSGIF_FRAME_INVALID;
+
+ gif->delay_min = 2;
+ gif->delay_default = 10;
+
+ *gif_out = gif;
+ return NSGIF_OK;
+}
+
+/**
+ * Read GIF header.
+ *
+ * 6-byte GIF file header is:
+ *
+ * +0 3CHARS Signature ('GIF')
+ * +3 3CHARS Version ('87a' or '89a')
+ *
+ * \param[in] gif The GIF object we're decoding.
+ * \param[in,out] pos The current buffer position, updated on success.
+ * \param[in] strict Whether to require a known GIF version.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_header(
+ struct nsgif *gif,
+ const uint8_t **pos,
+ bool strict)
+{
+ const uint8_t *data = *pos;
+ size_t len = gif->buf + gif->buf_len - data;
+
+ if (len < 6) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ if (strncmp((const char *) data, "GIF", 3) != 0) {
+ return NSGIF_ERR_DATA;
+ }
+ data += 3;
+
+ if (strict == true) {
+ if ((strncmp((const char *) data, "87a", 3) != 0) &&
+ (strncmp((const char *) data, "89a", 3) != 0)) {
+ return NSGIF_ERR_DATA;
+ }
+ }
+ data += 3;
+
+ *pos = data;
+ return NSGIF_OK;
+}
+
+/**
+ * Read Logical Screen Descriptor.
+ *
+ * 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
+ *
+ * \param[in] gif The GIF object we're decoding.
+ * \param[in,out] pos The current buffer position, updated on success.
+ * \return NSGIF_OK on success, appropriate error otherwise.
+ */
+static nsgif_error nsgif__parse_logical_screen_descriptor(
+ struct nsgif *gif,
+ const uint8_t **pos)
+{
+ const uint8_t *data = *pos;
+ size_t len = gif->buf + gif->buf_len - data;
+
+ if (len < 7) {
+ return NSGIF_ERR_END_OF_DATA;
+ }
+
+ gif->info.width = data[0] | (data[1] << 8);
+ gif->info.height = data[2] | (data[3] << 8);
+ gif->global_colours = data[4] & NSGIF_COLOUR_TABLE_MASK;
+ gif->colour_table_size = 2 << (data[4] & NSGIF_COLOUR_TABLE_SIZE_MASK);
+ gif->bg_index = data[5];
+ gif->aspect_ratio = data[6];
+ gif->info.loop_max = 1;
+
+ *pos += 7;
+ return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_data_scan(
+ nsgif_t *gif,
+ size_t size,
+ const uint8_t *data)
+{
+ const uint8_t *nsgif_data;
+ nsgif_error ret;
+ uint32_t frames;
+
+ /* Initialize values */
+ gif->buf_len = size;
+ gif->buf = data;
+
+ /* Get our current processing position */
+ nsgif_data = gif->buf + gif->buf_pos;
+
+ /* See if we should initialise the GIF */
+ if (gif->buf_pos == 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->frame_holders = 0;
+
+ /* The caller may have been lazy and not reset any values */
+ gif->info.frame_count = 0;
+ gif->frame_count_partial = 0;
+ gif->decoded_frame = NSGIF_FRAME_INVALID;
+ gif->frame = NSGIF_FRAME_INVALID;
+
+ ret = nsgif__parse_header(gif, &nsgif_data, false);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ ret = nsgif__parse_logical_screen_descriptor(gif, &nsgif_data);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ /* Remember we've done this now */
+ gif->buf_pos = nsgif_data - gif->buf;
+
+ /* 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->info.width == 640) && (gif->info.height == 480)) ||
+ ((gif->info.width == 640) && (gif->info.height == 512)) ||
+ ((gif->info.width == 800) && (gif->info.height == 600)) ||
+ ((gif->info.width == 1024) && (gif->info.height == 768)) ||
+ ((gif->info.width == 1280) && (gif->info.height == 1024)) ||
+ ((gif->info.width == 1600) && (gif->info.height == 1200)) ||
+ ((gif->info.width == 0) || (gif->info.height == 0)) ||
+ ((gif->info.width > 2048) || (gif->info.height > 2048))) {
+ gif->info.width = 1;
+ gif->info.height = 1;
+ }
+
+ /* 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] = NSGIF_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->buf_len == gif->buf_pos + 1) {
+ if (nsgif_data[0] == NSGIF_TRAILER) {
+ return NSGIF_OK;
+ }
+ }
+ }
+
+ /* 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] == NSGIF_PROCESS_COLOURS) {
+ /* Check for a global colour map signified by bit 7 */
+ if (gif->global_colours) {
+ ret = nsgif__colour_table_extract(gif,
+ gif->global_colour_table,
+ gif->colour_table_size,
+ &nsgif_data, true);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ gif->buf_pos = (nsgif_data - gif->buf);
+ } else {
+ /* Create a default colour table with the first two
+ * colours as black and white
+ */
+ uint32_t *entry = gif->global_colour_table;
+
+ entry[0] = 0x00000000;
+ /* Force Alpha channel to opaque */
+ ((uint8_t *) entry)[3] = 0xff;
+
+ entry[1] = 0xffffffff;
+ }
+
+ if (gif->global_colours &&
+ gif->bg_index < gif->colour_table_size) {
+ size_t bg_idx = gif->bg_index;
+ gif->bg_colour = gif->global_colour_table[bg_idx];
+ } else {
+ gif->bg_colour = gif->global_colour_table[0];
+ }
+ }
+
+ if (gif->lzw_ctx == NULL) {
+ lzw_result res = lzw_context_create(
+ (struct lzw_ctx **)&gif->lzw_ctx);
+ if (res != LZW_OK) {
+ return nsgif__error_from_lzw(res);
+ }
+ }
+
+ /* Try to initialise all frames. */
+ do {
+ frames = gif->info.frame_count;
+ ret = nsgif__process_frame(gif, frames, false);
+ } while (gif->info.frame_count > frames);
+
+ if (ret == NSGIF_ERR_END_OF_DATA && gif->info.frame_count > 0) {
+ ret = NSGIF_OK;
+ }
+
+ return ret;
+}
+
+static void nsgif__redraw_rect_extend(
+ const nsgif_rect_t *frame,
+ nsgif_rect_t *redraw)
+{
+ if (redraw->x1 == 0 || redraw->y1 == 0) {
+ *redraw = *frame;
+ } else {
+ if (redraw->x0 > frame->x0) {
+ redraw->x0 = frame->x0;
+ }
+ if (redraw->x1 < frame->x1) {
+ redraw->x1 = frame->x1;
+ }
+ if (redraw->y0 > frame->y0) {
+ redraw->y0 = frame->y0;
+ }
+ if (redraw->y1 < frame->y1) {
+ redraw->y1 = frame->y1;
+ }
+ }
+}
+
+static uint32_t nsgif__frame_next(
+ nsgif_t *gif,
+ bool partial,
+ uint32_t frame)
+{
+ uint32_t frames = partial ?
+ gif->frame_count_partial :
+ gif->info.frame_count;
+
+ if (frames == 0) {
+ return NSGIF_FRAME_INVALID;
+ }
+
+ frame++;
+ return (frame >= frames) ? 0 : frame;
+}
+
+static nsgif_error nsgif__next_displayable_frame(
+ nsgif_t *gif,
+ uint32_t *frame,
+ uint32_t *delay)
+{
+ uint32_t next = *frame;
+
+ do {
+ next = nsgif__frame_next(gif, false, next);
+ if (next == *frame || next == NSGIF_FRAME_INVALID) {
+ return NSGIF_ERR_FRAME_DISPLAY;
+ }
+
+ if (delay != NULL) {
+ *delay += gif->frames[next].info.delay;
+ }
+
+ } while (gif->frames[next].info.display == false);
+
+ *frame = next;
+ return NSGIF_OK;
+}
+
+static inline bool nsgif__animation_complete(int count, int max)
+{
+ if (max == 0) {
+ return false;
+ }
+
+ return (count >= max);
+}
+
+nsgif_error nsgif_reset(
+ nsgif_t *gif)
+{
+ gif->info.loop_count = 0;
+ gif->frame = NSGIF_FRAME_INVALID;
+
+ return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_frame_prepare(
+ nsgif_t *gif,
+ nsgif_rect_t *area,
+ uint32_t *delay_cs,
+ uint32_t *frame_new)
+{
+ nsgif_error ret;
+ nsgif_rect_t rect = {
+ .x1 = 0,
+ .y1 = 0,
+ };
+ uint32_t delay = 0;
+ uint32_t frame = gif->frame;
+
+ if (gif->frame != NSGIF_FRAME_INVALID &&
+ gif->frame < gif->info.frame_count &&
+ gif->frames[gif->frame].info.display) {
+ rect = gif->frames[gif->frame].info.rect;
+ }
+
+ if (nsgif__animation_complete(
+ gif->info.loop_count,
+ gif->info.loop_max)) {
+ return NSGIF_ERR_ANIMATION_END;
+ }
+
+ ret = nsgif__next_displayable_frame(gif, &frame, &delay);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ if (gif->frame != NSGIF_FRAME_INVALID && frame < gif->frame) {
+ gif->info.loop_count++;
+ }
+
+ if (gif->info.frame_count == 1) {
+ delay = NSGIF_INFINITE;
+
+ } else if (gif->info.loop_max != 0) {
+ uint32_t frame_next = frame;
+ ret = nsgif__next_displayable_frame(gif, &frame_next, NULL);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ if (frame_next < frame) {
+ if (nsgif__animation_complete(
+ gif->info.loop_count + 1,
+ gif->info.loop_max)) {
+ delay = NSGIF_INFINITE;
+ }
+ }
+ }
+
+ gif->frame = frame;
+ nsgif__redraw_rect_extend(&gif->frames[frame].info.rect, &rect);
+
+ *frame_new = gif->frame;
+ *delay_cs = delay;
+ *area = rect;
+
+ return NSGIF_OK;
+}
+
+/* exported function documented in nsgif.h */
+nsgif_error nsgif_frame_decode(
+ nsgif_t *gif,
+ uint32_t frame,
+ nsgif_bitmap_t **bitmap)
+{
+ uint32_t start_frame;
+ nsgif_error ret = NSGIF_OK;
+
+ if (frame > gif->info.frame_count) {
+ return NSGIF_ERR_BAD_FRAME;
+ }
+
+ if (gif->decoded_frame == frame) {
+ *bitmap = gif->frame_image;
+ return NSGIF_OK;
+
+ } else if (gif->decoded_frame >= frame ||
+ gif->decoded_frame == NSGIF_FRAME_INVALID) {
+ /* Can skip to first frame or restart. */
+ start_frame = 0;
+ } else {
+ start_frame = nsgif__frame_next(
+ gif, false, gif->decoded_frame);
+ }
+
+ for (uint32_t f = start_frame; f <= frame; f++) {
+ ret = nsgif__process_frame(gif, f, true);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+ }
+
+ *bitmap = gif->frame_image;
+ return ret;
+}
+
+/* exported function documented in nsgif.h */
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif)
+{
+ return &gif->info;
+}
+
+/* exported function documented in nsgif.h */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif_t *gif,
+ uint32_t frame)
+{
+ if (frame > gif->info.frame_count) {
+ return NULL;
+ }
+
+ return &gif->frames[frame].info;
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_strerror(nsgif_error err)
+{
+ static const char *const str[] = {
+ [NSGIF_OK] = "Success",
+ [NSGIF_ERR_OOM] = "Out of memory",
+ [NSGIF_ERR_DATA] = "Invalid source data",
+ [NSGIF_ERR_BAD_FRAME] = "Requested frame does not exist",
+ [NSGIF_ERR_DATA_FRAME] = "Invalid frame data",
+ [NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames",
+ [NSGIF_ERR_END_OF_DATA] = "Insufficient data for first frame",
+ [NSGIF_ERR_END_OF_FRAME] = "End of data during frame",
+ [NSGIF_ERR_FRAME_DISPLAY] = "Frame can't be displayed",
+ [NSGIF_ERR_ANIMATION_END] = "Animation complete",
+ };
+
+ if (err >= NSGIF_ARRAY_LEN(str) || str[err] == NULL) {
+ return "Unknown error";
+ }
+
+ return str[err];
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal)
+{
+ static const char *const str[] = {
+ [NSGIF_DISPOSAL_UNSPECIFIED] = "Unspecified",
+ [NSGIF_DISPOSAL_NONE] = "None",
+ [NSGIF_DISPOSAL_RESTORE_BG] = "Restore background",
+ [NSGIF_DISPOSAL_RESTORE_PREV] = "Restore previous",
+ [NSGIF_DISPOSAL_RESTORE_QUIRK] = "Restore quirk",
+ };
+
+ if (disposal >= NSGIF_ARRAY_LEN(str) || str[disposal] == NULL) {
+ return "Unspecified";
+ }
+
+ return str[disposal];
+}
diff --git a/src/libnsgif.c b/src/libnsgif.c
deleted file mode 100644
index 25f98c6..0000000
--- a/src/libnsgif.c
+++ /dev/null
@@ -1,1461 +0,0 @@
-/*
- * Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
- * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-#include <assert.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-
-#include "lzw.h"
-#include "libnsgif.h"
-
-/**
- *
- * \file
- * \brief GIF image decoder
- *
- * The GIF format is thoroughly documented; a full description can be found at
- * http://www.w3.org/Graphics/GIF/spec-gif89a.txt
- *
- * \todo Plain text and comment extensions should be implemented.
- */
-
-/** Maximum colour table size */
-#define GIF_MAX_COLOURS 256
-
-/** Internal flag that the colour table needs to be processed */
-#define GIF_PROCESS_COLOURS 0xaa000000
-
-/** Internal flag that a frame is invalid/unprocessed */
-#define GIF_INVALID_FRAME -1
-
-/** Transparent colour */
-#define GIF_TRANSPARENT_COLOUR 0x00
-
-/** No transparency */
-#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu)
-
-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
-#define GIF_EXTENSION_INTRODUCER 0x21
-#define GIF_EXTENSION_GRAPHIC_CONTROL 0xf9
-#define GIF_DISPOSAL_MASK 0x1c
-#define GIF_TRANSPARENCY_MASK 0x01
-#define GIF_EXTENSION_COMMENT 0xfe
-#define GIF_EXTENSION_PLAIN_TEXT 0x01
-#define GIF_EXTENSION_APPLICATION 0xff
-#define GIF_BLOCK_TERMINATOR 0x00
-#define GIF_TRAILER 0x3b
-
-/**
- * Convert an LZW result code to equivalent GIF result code.
- *
- * \param[in] l_res LZW response code.
- * \return GIF result code.
- */
-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_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
- *
- * \param gif The animation context
- * \param width The width of the sprite
- * \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(
- struct gif_animation *gif,
- uint32_t width,
- uint32_t height)
-{
- /* 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;
-}
-
-/**
- * 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] frame 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;
- 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;
- }
-
- bitmap = gif__bitmap_get(gif);
- if (bitmap == NULL) {
- 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, bitmap, 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 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;
-
- if (prev_frame == NULL) {
- return GIF_FRAME_DATA_ERROR;
- }
-
- for (unsigned y = 0; y < height; y++) {
- memcpy(bitmap, prev_frame, width * 4);
-
- bitmap += gif->width;
- prev_frame += gif->prev_width;
- }
-
- return GIF_OK;
-}
-
-/**
- * Get the next line for GIF decode.
- *
- * Note that the step size must be initialised to 24 at the start of the frame
- * (when y == 0). This is because of the first two passes of the frame have
- * the same step size of 8, and the step size is used to determine the current
- * pass.
- *
- * \param[in] height Frame height in pixels.
- * \param[in,out] y Current row, starting from 0, updated on exit.
- * \param[in,out] step Current step starting with 24, updated on exit.
- * \return true if there is a row to process, false at the end of the frame.
- */
-static inline bool gif__deinterlace(uint32_t height, uint32_t *y, uint8_t *step)
-{
- *y += *step & 0xf;
-
- if (*y < height) return true;
-
- switch (*step) {
- case 24: *y = 4; *step = 8; if (*y < height) return true;
- /* Fall through. */
- case 8: *y = 2; *step = 4; if (*y < height) return true;
- /* Fall through. */
- case 4: *y = 1; *step = 2; if (*y < height) return true;
- /* Fall through. */
- default:
- break;
- }
-
- return false;
-}
-
-/**
- * Get the next line for GIF decode.
- *
- * \param[in] interlace Non-zero if the frame is not interlaced.
- * \param[in] height Frame height in pixels.
- * \param[in,out] y Current row, starting from 0, updated on exit.
- * \param[in,out] step Current step starting with 24, updated on exit.
- * \return true if there is a row to process, false at the end of the frame.
- */
-static inline bool gif__next_row(uint32_t interlace,
- uint32_t height, uint32_t *y, uint8_t *step)
-{
- if (!interlace) {
- return (++*y != height);
- } else {
- return gif__deinterlace(height, y, step);
- }
-}
-
-/**
- * Get any frame clip adjustment for the image extent.
- *
- * \param[in] frame_off Frame's X or Y offset.
- * \param[in] frame_dim Frame width or height.
- * \param[in] image_ext Image width or height constraint.
- * \return the amount the frame needs to be clipped to fit the image in given
- * dimension.
- */
-static inline uint32_t gif__clip(
- uint32_t frame_off,
- uint32_t frame_dim,
- uint32_t image_ext)
-{
- uint32_t frame_ext = frame_off + frame_dim;
-
- if (frame_ext <= image_ext) {
- return 0;
- }
-
- return frame_ext - image_ext;
-}
-
-/**
- * Perform any jump over decoded data, to accommodate clipped portion of frame.
- *
- * \param[in,out] skip Number of pixels of data to jump.
- * \param[in,out] available Number of pixels of data currently available.
- * \param[in,out] pos Position in decoded pixel value data.
- */
-static inline void gif__jump_data(
- uint32_t *skip,
- uint32_t *available,
- const uint8_t **pos)
-{
- uint32_t jump = (*skip < *available) ? *skip : *available;
-
- *skip -= jump;
- *available -= jump;
- *pos += jump;
-}
-
-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,
- const uint8_t *data,
- uint32_t transparency_index,
- uint32_t *restrict frame_data,
- uint32_t *restrict colour_table)
-{
- lzw_result res;
- uint32_t clip_x = gif__clip(offset_x, width, gif->width);
- uint32_t clip_y = gif__clip(offset_y, height, gif->height);
- const uint8_t *uncompressed;
- gif_result ret = GIF_OK;
- uint32_t available = 0;
- uint8_t step = 24;
- uint32_t skip = 0;
- uint32_t y = 0;
-
- if (offset_x >= gif->width ||
- offset_y >= gif->height) {
- return GIF_OK;
- }
-
- width -= clip_x;
- height -= clip_y;
-
- if (width == 0 || height == 0) {
- return GIF_OK;
- }
-
- /* Initialise the LZW decoding */
- res = lzw_decode_init(gif->lzw_ctx, data[0],
- gif->gif_data, gif->buffer_size,
- data + 1 - gif->gif_data);
- if (res != LZW_OK) {
- return gif__error_from_lzw(res);
- }
-
- do {
- uint32_t x;
- uint32_t *frame_scanline;
-
- frame_scanline = frame_data + offset_x +
- (y + offset_y) * gif->width;
-
- x = width;
- while (x > 0) {
- unsigned row_available;
- while (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);
- }
- return ret;
- }
- res = lzw_decode(gif->lzw_ctx,
- &uncompressed, &available);
-
- if (available == 0) {
- return GIF_OK;
- }
- gif__jump_data(&skip, &available, &uncompressed);
- }
-
- 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++;
- }
- }
- }
-
- skip = clip_x;
- gif__jump_data(&skip, &available, &uncompressed);
- } while (gif__next_row(interlace, height, &y, &step));
-
- return ret;
-}
-
-static gif_result gif__decode_simple(
- struct gif_animation *gif,
- uint32_t height,
- uint32_t offset_y,
- const uint8_t *data,
- 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;
-
- if (offset_y >= gif->height) {
- return GIF_OK;
- }
-
- height -= gif__clip(offset_y, height, gif->height);
-
- if (height == 0) {
- return GIF_OK;
- }
-
- /* Initialise the LZW decoding */
- res = lzw_decode_init_map(gif->lzw_ctx, data[0],
- transparency_index, colour_table,
- gif->gif_data, gif->buffer_size,
- data + 1 - gif->gif_data);
- if (res != LZW_OK) {
- return gif__error_from_lzw(res);
- }
-
- 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;
- }
- }
-
- if (pixels == 0) {
- ret = GIF_OK;
- }
-
- return ret;
-}
-
-static inline gif_result gif__decode(
- struct gif_animation *gif,
- struct gif_frame *frame,
- const uint8_t *data,
- 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,
- data, transparency_index,
- frame_data, colour_table);
- } else {
- ret = gif__decode_complex(gif, width, height,
- offset_x, offset_y, interlace,
- data, transparency_index,
- frame_data, colour_table);
- }
-
- return ret;
-}
-
-/**
- * Restore a GIF to the background colour.
- *
- * \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;
-
- width -= gif__clip(offset_x, width, gif->width);
- height -= gif__clip(offset_y, height, gif->height);
-
- if (frame->display == false || width == 0) {
- 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,
- const uint8_t *data,
- uint32_t frame_idx)
-{
- 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];
-
- 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, data, 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;
-}
-
-/**
- * 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_DATA if more data is needed,
- * GIF_OK for success.
- */
-static gif_result gif__parse_extension_graphic_control(
- struct gif_frame *frame,
- const 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_DATA;
- }
-
- frame->frame_delay = data[3] | (data[4] << 8);
- if (data[2] & GIF_TRANSPARENCY_MASK) {
- frame->transparency = true;
- frame->transparency_index = data[5];
- }
-
- 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;
- }
-
- /* 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_DATA if more data is needed,
- * GIF_OK for success.
- */
-static gif_result gif__parse_extension_application(
- struct gif_animation *gif,
- const 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_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;
-}
-
-/**
- * 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] pos Current position in data, updated on exit.
- * \param[in] decode Whether to decode or skip over the extension.
- * \return GIF_INSUFFICIENT_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,
- const uint8_t **pos,
- bool decode)
-{
- const uint8_t *gif_data = *pos;
- const uint8_t *gif_end = gif->gif_data + gif->buffer_size;
- int 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_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;
-
- 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) {
- return GIF_INSUFFICIENT_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. */
- 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_DATA;
- }
- }
- gif_data++;
- gif_bytes = gif_end - gif_data;
- }
-
- if (gif_data > gif_end) {
- gif_data = gif_end;
- }
-
- /* Set buffer position and return */
- *pos = 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.
- * \param[in] pos Current position in data, updated on exit.
- * \param[in] decode Whether to decode the image descriptor.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_image_descriptor(
- struct gif_animation *gif,
- struct gif_frame *frame,
- const uint8_t **pos,
- bool decode)
-{
- const uint8_t *data = *pos;
- size_t len = gif->gif_data + gif->buffer_size - data;
- enum {
- GIF_IMAGE_DESCRIPTOR_LEN = 10u,
- GIF_IMAGE_SEPARATOR = 0x2Cu,
- };
-
- assert(gif != NULL);
- assert(frame != NULL);
-
- if (len < GIF_IMAGE_DESCRIPTOR_LEN) {
- return GIF_INSUFFICIENT_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;
-
- /* Allow first frame to grow image dimensions. */
- if (gif->frame_count == 0) {
- if (x + w > gif->width) {
- gif->width = x + w;
- }
- if (y + h > gif->height) {
- gif->height = y + h;
- }
- }
- }
-
- *pos += GIF_IMAGE_DESCRIPTOR_LEN;
- return GIF_OK;
-}
-
-/**
- * Extract a GIF colour table into a LibNSGIF colour table buffer.
- *
- * \param[in] gif The gif object we're decoding.
- * \param[in] colour_table The colour table to populate.
- * \param[in] colour_table_entries The number of colour table entries.
- * \param[in] pos Current position in data, updated on exit.
- * \param[in] decode Whether to decode the colour table.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__colour_table_extract(
- struct gif_animation *gif,
- uint32_t *colour_table,
- size_t colour_table_entries,
- const uint8_t **pos,
- bool decode)
-{
- const uint8_t *data = *pos;
- size_t len = gif->gif_data + gif->buffer_size - data;
-
- if (len < colour_table_entries * 3) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- if (decode) {
- int count = colour_table_entries;
- uint8_t *entry = (uint8_t *)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 */
- }
- }
-
- *pos += colour_table_entries * 3;
- 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.
- * \param[in] pos Current position in data, updated on exit.
- * \param[in] decode Whether to decode the colour table.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_colour_table(
- struct gif_animation *gif,
- struct gif_frame *frame,
- const uint8_t **pos,
- bool decode)
-{
- gif_result ret;
-
- assert(gif != NULL);
- assert(frame != NULL);
-
- if ((frame->flags & GIF_COLOUR_TABLE_MASK) == 0) {
- gif->colour_table = gif->global_colour_table;
- return GIF_OK;
- }
-
- ret = gif__colour_table_extract(gif, gif->local_colour_table,
- 2 << (frame->flags & GIF_COLOUR_TABLE_SIZE_MASK),
- pos, decode);
- if (ret != GIF_OK) {
- return ret;
- }
-
- gif->colour_table = gif->local_colour_table;
- return GIF_OK;
-}
-
-/**
- * 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 parse image data for.
- * \param[in] pos Current position in data, updated on exit.
- * \param[in] decode Whether to decode the image data.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_image_data(
- struct gif_animation *gif,
- struct gif_frame *frame,
- const uint8_t **pos,
- bool decode)
-{
- const uint8_t *data = *pos;
- size_t len = gif->gif_data + gif->buffer_size - data;
- 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) {
- default: if (data[0] == GIF_TRAILER) return GIF_OK;
- break;
- 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_DATA;
- }
-
- minimum_code_size = data[0];
- if (minimum_code_size >= LZW_CODE_MAX) {
- return GIF_DATA_ERROR;
- }
-
- if (decode) {
- ret = gif__update_bitmap(gif, frame, data, frame_idx);
- } else {
- uint32_t block_size = 0;
-
- /* Skip the minimum code size. */
- data++;
- len--;
-
- while (block_size != 1) {
- if (len < 1) return GIF_INSUFFICIENT_DATA;
- block_size = data[0] + 1;
- /* Check if the frame data runs off the end of the file */
- if (block_size > len) {
- block_size = len;
- return GIF_OK;
- }
-
- len -= block_size;
- data += block_size;
- }
-
- gif->frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
- *pos = data;
-
- /* Check if we've finished */
- if (len < 1) {
- return GIF_INSUFFICIENT_DATA;
- } else {
- if (data[0] == GIF_TRAILER) {
- return GIF_OK;
- }
- }
-
- return GIF_WORKING;
- }
-
- 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.
- * \param[in] decode Whether to decode the graphical image data.
- * \return error code
- * - GIF_INSUFFICIENT_DATA reached unexpected end of source data.
- * - GIF_FRAME_DATA_ERROR for GIF frame data error
- * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - 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__process_frame(
- struct gif_animation *gif,
- uint32_t frame_idx,
- bool decode)
-{
- gif_result ret;
- const uint8_t *pos;
- const uint8_t *end;
- struct gif_frame *frame;
-
- frame = gif__get_frame(gif, frame_idx);
- if (frame == NULL) {
- return GIF_INSUFFICIENT_MEMORY;
- }
-
- end = gif->gif_data + gif->buffer_size;
-
- if (decode) {
- pos = gif->gif_data + frame->frame_pointer;
-
- /* Ensure this frame is supposed to be decoded */
- if (frame->display == false) {
- return GIF_OK;
- }
-
- /* Ensure the frame is in range to decode */
- if (frame_idx > gif->frame_count_partial) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- /* Done if frame is already decoded */
- if ((int)frame_idx == gif->decoded_frame) {
- return GIF_OK;
- }
- } else {
- pos = (uint8_t *)(gif->gif_data + gif->buffer_position);
-
- /* Check if we've finished */
- if (pos < end && pos[0] == GIF_TRAILER) {
- return GIF_OK;
- }
-
- /* 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;
- }
- }
-
- /* Initialise any extensions */
- ret = gif__parse_frame_extensions(gif, frame, &pos, !decode);
- if (ret != GIF_OK) {
- goto cleanup;
- }
-
- ret = gif__parse_image_descriptor(gif, frame, &pos, !decode);
- if (ret != GIF_OK) {
- goto cleanup;
- }
-
- ret = gif__parse_colour_table(gif, frame, &pos, decode);
- if (ret != GIF_OK) {
- goto cleanup;
- }
-
- ret = gif__parse_image_data(gif, frame, &pos, decode);
- if (ret != GIF_OK) {
- goto cleanup;
- }
-
-cleanup:
- if (!decode) {
- gif->buffer_position = pos - gif->gif_data;
- }
-
- return ret;
-}
-
-/* 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;
-}
-
-/**
- * Read GIF header.
- *
- * 6-byte GIF file header is:
- *
- * +0 3CHARS Signature ('GIF')
- * +3 3CHARS Version ('87a' or '89a')
- *
- * \param[in] gif The GIF object we're decoding.
- * \param[in,out] pos The current buffer position, updated on success.
- * \param[in] strict Whether to require a known GIF version.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_header(
- struct gif_animation *gif,
- const uint8_t **pos,
- bool strict)
-{
- const uint8_t *data = *pos;
- size_t len = gif->gif_data + gif->buffer_size - data;
-
- if (len < 6) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- if (strncmp((const char *) data, "GIF", 3) != 0) {
- return GIF_DATA_ERROR;
- }
- data += 3;
-
- if (strict == true) {
- if ((strncmp((const char *) data, "87a", 3) != 0) &&
- (strncmp((const char *) data, "89a", 3) != 0)) {
- return GIF_DATA_ERROR;
- }
- }
- data += 3;
-
- *pos = data;
- return GIF_OK;
-}
-
-/**
- * Read Logical Screen Descriptor.
- *
- * 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
- *
- * \param[in] gif The GIF object we're decoding.
- * \param[in,out] pos The current buffer position, updated on success.
- * \return GIF_OK on success, appropriate error otherwise.
- */
-static gif_result gif__parse_logical_screen_descriptor(
- struct gif_animation *gif,
- const uint8_t **pos)
-{
- const uint8_t *data = *pos;
- size_t len = gif->gif_data + gif->buffer_size - data;
-
- if (len < 7) {
- return GIF_INSUFFICIENT_DATA;
- }
-
- gif->width = data[0] | (data[1] << 8);
- gif->height = data[2] | (data[3] << 8);
- gif->global_colours = data[4] & GIF_COLOUR_TABLE_MASK;
- gif->colour_table_size = 2 << (data[4] & GIF_COLOUR_TABLE_SIZE_MASK);
- gif->bg_index = data[5];
- gif->aspect_ratio = data[6];
- gif->loop_count = 1;
-
- *pos += 7;
- return GIF_OK;
-}
-
-/* exported function documented in libnsgif.h */
-gif_result gif_initialise(gif_animation *gif, size_t size, const uint8_t *data)
-{
- const uint8_t *gif_data;
- gif_result ret;
-
- /* Initialize values */
- gif->buffer_size = size;
- gif->gif_data = 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->frame_holders = 0;
- 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;
-
- ret = gif__parse_header(gif, &gif_data, false);
- if (ret != GIF_OK) {
- return ret;
- }
-
- ret = gif__parse_logical_screen_descriptor(gif, &gif_data);
- if (ret != GIF_OK) {
- return ret;
- }
-
- /* Remember we've done this now */
- gif->buffer_position = gif_data - gif->gif_data;
-
- /* 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(uint32_t));
- gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(uint32_t));
- 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->buffer_position + 1) {
- if (gif_data[0] == GIF_TRAILER) {
- return GIF_OK;
- }
- }
- }
-
- /* 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) {
- ret = gif__colour_table_extract(gif,
- gif->global_colour_table,
- gif->colour_table_size,
- &gif_data, true);
- if (ret != GIF_OK) {
- return ret;
- }
-
- gif->buffer_position = (gif_data - gif->gif_data);
- } else {
- /* Create a default colour table with the first two
- * colours as black and white
- */
- uint32_t *entry = gif->global_colour_table;
-
- entry[0] = 0x00000000;
- /* Force Alpha channel to opaque */
- ((uint8_t *) entry)[3] = 0xff;
-
- entry[1] = 0xffffffff;
- }
-
- if (gif->global_colours &&
- gif->bg_index < gif->colour_table_size) {
- size_t bg_idx = gif->bg_index;
- gif->bg_colour = gif->global_colour_table[bg_idx];
- } else {
- gif->bg_colour = gif->global_colour_table[0];
- }
- }
-
- 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);
- }
- }
-
- /* Repeatedly try to initialise frames */
- do {
- ret = gif__process_frame(gif, gif->frame_count, false);
- } while (ret == GIF_WORKING);
-
- return ret;
-}
-
-/* exported function documented in libnsgif.h */
-gif_result gif_decode_frame(gif_animation *gif, uint32_t frame)
-{
- return gif__process_frame(gif, frame, true);
-}
-
-/* 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;
-}
diff --git a/test/Makefile b/test/Makefile
index f067d81..a578aef 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,3 +1,3 @@
-DIR_TEST_ITEMS := decode_gif:decode_gif.c
+DIR_TEST_ITEMS := nsgif:nsgif.c
include $(NSBUILD)/Makefile.subdir
diff --git a/test/cli.c b/test/cli.c
new file mode 100644
index 0000000..031cd97
--- /dev/null
+++ b/test/cli.c
@@ -0,0 +1,763 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cli.h"
+
+/**
+ * Check whether a CLI argument type should have a numerical value.
+ *
+ * \param[in] type An argument type.
+ * \return true if the argument needs a numerical value, or false otherwise.
+ */
+static inline bool cli__arg_is_numerical(enum cli_arg_type type)
+{
+ return (type != CLI_STRING && type != CLI_BOOL);
+}
+
+/**
+ * Parse a signed integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] i Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_int(
+ const char *str,
+ int64_t *i,
+ size_t *pos)
+{
+ long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoll(str, &end, 0);
+
+ if (end == str || errno == ERANGE ||
+ temp > INT64_MAX || temp < INT64_MIN) {
+ fprintf(stderr, "Failed to parse integer from '%s'\n", str);
+ return false;
+ }
+
+ *i = (int64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an unsigned integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] u Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_uint(
+ const char *str,
+ uint64_t *u,
+ size_t *pos)
+{
+ unsigned long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoull(str, &end, 0);
+
+ if (end == str || errno == ERANGE || temp > UINT64_MAX) {
+ fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
+ return false;
+ }
+
+ *u = (uint64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an enum value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] e Enum details.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_enum(
+ const char *str,
+ const struct cli_enum *e,
+ size_t *pos)
+{
+ str += *pos;
+ *pos += strlen(str);
+
+ for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
+ if (strcmp(str, sv->str) == 0) {
+ *e->e = sv->val;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Parse a string value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] s Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_string(
+ const char *str,
+ const char **s,
+ size_t *pos)
+{
+ *s = str + *pos;
+ *pos += strlen(*s);
+ return true;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse a value from.
+ * \param[in,out] pos Current position in argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value(
+ const struct cli_table_entry *entry,
+ const char *arg,
+ size_t *pos)
+{
+ switch (entry->t) {
+ case CLI_CMD:
+ if (strcmp(arg + *pos, entry->l) == 0) {
+ *pos += strlen(arg);
+ return true;
+ }
+ return false;
+
+ case CLI_INT:
+ return cli__parse_value_int(arg, entry->v.i, pos);
+
+ case CLI_UINT:
+ return cli__parse_value_uint(arg, entry->v.u, pos);
+
+ case CLI_ENUM:
+ return cli__parse_value_enum(arg, &entry->v.e, pos);
+
+ case CLI_STRING:
+ return cli__parse_value_string(arg, entry->v.s, pos);
+
+ default:
+ fprintf(stderr, "Unexpected value for '%s': %s\n",
+ entry->l, arg);
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in] arg_pos Current position in argv.
+ * \param[in,out] pos Current pos in current argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_argv_value(const struct cli_table_entry *entry,
+ int argc, const char **argv,
+ int arg_pos, size_t *pos)
+{
+ const char *arg = argv[arg_pos];
+
+ if (arg_pos >= argc) {
+ fprintf(stderr, "Value not given for '%s'\n", entry->l);
+ return false;
+ }
+
+ return cli__parse_value(entry, arg, pos);
+}
+
+/**
+ * Check whether a CLI argument is a positional value.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \return true if the argument is positional, or false otherwise.
+ */
+static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
+{
+ return entry->p;
+}
+
+/**
+ * Look up a short argument flag.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] s Argument flag to look up in client CLI spec.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_short(
+ const struct cli_table *cli, char s)
+{
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ continue;
+ }
+ if (cli->entries[i].s == s) {
+ return &cli->entries[i];
+ }
+ }
+
+ fprintf(stderr, "Unknown flag: '%c'\n", s);
+ return NULL;
+}
+
+/**
+ * Handle an argument with a type that requires a value.
+ *
+ * This can handle the value being in the current argument, optionally split by
+ * a separator, or in the next argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in,out] arg_pos Current position in argv, updated on exit.
+ * \param[in] pos Current position in current argument string.
+ * \param[in] sep Name/value separator character, or '\0' if none.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__handle_arg_value(const struct cli_table_entry *entry,
+ int argc, const char **argv, int *arg_pos, size_t pos, char sep)
+{
+ const char *arg = argv[*arg_pos];
+ size_t orig_pos;
+ bool ret;
+
+ if (arg[pos] == '\0') {
+ (*arg_pos)++;
+ pos = 0;
+ } else if (arg[pos] == sep) {
+ pos++;
+ } else if (cli__arg_is_numerical(entry->t) == false) {
+ fprintf(stderr, "Separator required for non-numerical value\n");
+ return false;
+ }
+
+ if (isspace(argv[*arg_pos][pos])) {
+ fprintf(stderr, "Unexpected white space in '%s' "
+ "for argument '%s'\n",
+ &argv[*arg_pos][pos], entry->l);
+ return false;
+ }
+
+ orig_pos = pos;
+ ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
+ if (ret != true) {
+ return ret;
+ }
+
+ if (argv[*arg_pos][pos] != '\0') {
+ fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
+ &argv[*arg_pos][orig_pos], entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a flags argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_short(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const char *arg = argv[*arg_pos];
+ size_t pos = 1;
+
+ if (arg[0] != '-') {
+ return false;
+ }
+
+ while (arg[pos] != '\0') {
+ const struct cli_table_entry *entry;
+
+ entry = cli__lookup_short(cli, arg[pos]);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ *entry->v.b = true;
+ } else {
+ return cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos + 1, '\0');
+ }
+
+ pos++;
+ }
+
+ return true;
+}
+
+/**
+ * Look up a long argument name.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument name to look up in cli spec.
+ * \param[in,out] pos Current position in arg, updated on exit.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_long(
+ const struct cli_table *cli,
+ const char *arg,
+ size_t *pos)
+{
+ arg += *pos;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i]) == false) {
+ const char *name = cli->entries[i].l;
+ size_t name_len = strlen(cli->entries[i].l);
+
+ if (strncmp(name, arg, name_len) == 0) {
+ if (arg[name_len] != '\0' &&
+ arg[name_len] != '=') {
+ continue;
+ }
+ *pos += name_len;
+ return &cli->entries[i];
+ }
+ }
+ }
+
+ fprintf(stderr, "Unknown argument: '%s'\n", arg);
+ return NULL;
+}
+
+/**
+ * Parse a long argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_long(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const struct cli_table_entry *entry;
+ const char *arg = argv[*arg_pos];
+ size_t pos = 2;
+
+ if (arg[0] != '-' ||
+ arg[1] != '-') {
+ return false;
+ }
+
+ entry = cli__lookup_long(cli, arg, &pos);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ if (arg[pos] != '\0') {
+ fprintf(stderr, "Unexpected value for argument '%s'\n",
+ arg);
+ return false;
+ }
+ *entry->v.b = true;
+ } else {
+ bool ret;
+
+ ret = cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos, '=');
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument according to the given CLI spec entry.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional_entry(
+ const struct cli_table_entry *entry,
+ const char *arg)
+{
+ size_t pos = 0;
+ bool ret;
+
+ ret = cli__parse_value(entry, arg, &pos);
+ if (ret != true) {
+ return ret;
+ } else if (arg[pos] != '\0') {
+ fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
+ arg, entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument to parse.
+ * \param[in] count Number of positional arguments parsed already.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional(const struct cli_table *cli,
+ const char *arg, size_t count)
+{
+ size_t positional = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ if (positional == count) {
+ return cli__parse_positional_entry(
+ &cli->entries[i], arg);
+ }
+
+ positional++;
+ }
+ }
+
+ fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
+ return false;
+}
+
+/**
+ * Get the string to indicate type of value expected for an argument.
+ *
+ * \param[in] type The argument type.
+ * \return String for value type.
+ */
+static const char *cli__string_from_type(enum cli_arg_type type)
+{
+ static const char *const strings[] = {
+ [CLI_BOOL] = "",
+ [CLI_INT] = "INT",
+ [CLI_UINT] = "UINT",
+ [CLI_ENUM] = "ENUM",
+ [CLI_STRING] = "STRING",
+ };
+
+ if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
+ return "";
+ }
+
+ return strings[type];
+}
+
+/**
+ * Helper to update a maximum adjusted string length if new values is greater.
+ *
+ * \param[in] str String to check.
+ * \param[in] adjustment Amount to modify length of string by (bytes).
+ * \param[out] len Returns the maximum of existing and this length.
+ */
+static void cli__max_len(const char *str, size_t adjustment, size_t *len)
+{
+ size_t str_len = strlen(str) + adjustment;
+
+ if (str_len > *len) {
+ *len = str_len;
+ }
+}
+
+/**
+ * Count up various properties of the client CLI interface specification.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[out] count Returns number of non-positional arguments.
+ * \param[out] pcount Returns number of positional arguments.
+ * \param[out] max_len Returns max string length of non-positional arguments.
+ * \param[out] pmax_len Returns max string length of positional arguments.
+ * \param[out] phas_desc Returns number of positional args with descriptions.
+ */
+static void cli__count(const struct cli_table *cli,
+ size_t *count,
+ size_t *pcount,
+ size_t *max_len,
+ size_t *pmax_len,
+ size_t *phas_desc)
+{
+ if (count != NULL) *count = 0;
+ if (pcount != NULL) *pcount = 0;
+ if (max_len != NULL) *max_len = 0;
+ if (pmax_len != NULL) *pmax_len = 0;
+ if (phas_desc != NULL) *phas_desc = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (cli__entry_is_positional(entry)) {
+ if (pcount != NULL) {
+ (*pcount)++;
+ }
+ if (pmax_len != NULL) {
+ cli__max_len(entry->l, 0, pmax_len);
+ }
+ if (phas_desc != NULL) {
+ (*phas_desc)++;
+ }
+ } else {
+ if (count != NULL) {
+ (*count)++;
+ }
+ if (max_len != NULL) {
+ const char *type_str;
+ size_t type_len;
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+
+ cli__max_len(entry->l, type_len, max_len);
+ }
+ }
+ }
+}
+
+static inline bool cli__is_negative(const char *arg)
+{
+ int64_t i;
+ size_t pos = 0;
+
+ return cli__parse_value_int(arg, &i, &pos)
+ && pos == strlen(arg)
+ && i < 0;
+}
+
+/* Documented in cli.h */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
+{
+ size_t pos_count = 0;
+ enum {
+ ARG_PROG_NAME,
+ ARG_FIRST,
+ };
+
+ for (int i = ARG_FIRST; i < argc; i++) {
+ const char *arg = argv[i];
+ size_t pos_inc = 0;
+ bool ret;
+
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ ret = cli__parse_long(cli, argc, argv, &i);
+ } else {
+ ret = cli__parse_short(cli, argc, argv, &i);
+ if (ret != true) {
+ if (cli__is_negative(argv[i])) {
+ pos_inc = 1;
+ ret = cli__parse_positional(
+ cli, argv[i],
+ pos_count);
+ }
+ }
+ }
+ } else {
+ pos_inc = 1;
+ ret = cli__parse_positional(cli, argv[i], pos_count);
+ }
+
+ if (ret != true) {
+ return ret;
+ }
+
+ pos_count += pos_inc;
+ }
+
+ if (pos_count < cli->min_positional) {
+ fprintf(stderr, "Insufficient positional arguments found.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Get terminal width.
+ *
+ * \return terminal width in characters.
+ */
+static size_t cli__terminal_width(void)
+{
+ return 80;
+}
+
+/**
+ * Print an entry's description, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] entry The entry to print the description for.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_description(const struct cli_table_entry *entry,
+ size_t indent)
+{
+ size_t terminal_width = cli__terminal_width();
+ size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
+ size_t space = avail;
+ const char *desc = entry->d;
+
+ if (desc != NULL) {
+ while (*desc != '\0') {
+ size_t word_len = strcspn(desc, " \n\t");
+ if (word_len <= space || space == avail) {
+ fprintf(stderr, "%*.*s",
+ (int)word_len,
+ (int)word_len, desc);
+ desc += word_len;
+ if (word_len <= space) {
+ space -= word_len;
+ }
+ if (space > 0) {
+ fprintf(stderr, " ");
+ space--;
+ }
+ } else {
+ fprintf(stderr, "\n%*s", (int)indent, "");
+ space = avail;
+ }
+ desc += strspn(desc, " \n\t");
+ }
+ }
+
+ fprintf(stderr, "\n");
+}
+
+/* Documented in cli.h */
+void cli_help(const struct cli_table *cli, const char *prog_name)
+{
+ size_t count;
+ size_t pcount;
+ size_t max_len;
+ size_t pmax_len;
+ size_t phas_desc;
+ size_t required = 0;
+ enum {
+ ARG_PROG_NAME,
+ };
+
+ cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
+
+ fprintf(stderr, "\nUsage: %s", prog_name);
+
+ if (pcount > 0) {
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ const char *punctuation =
+ (required == cli->min_positional) ?
+ " [" : " ";
+
+ if (cli->entries[i].t == CLI_CMD) {
+ fprintf(stderr, "%s%s", punctuation,
+ cli->entries[i].l);
+ } else {
+ fprintf(stderr, "%s<%s>", punctuation,
+ cli->entries[i].l);
+ }
+ required++;
+ }
+ }
+ if (required == pcount && required > cli->min_positional) {
+ fprintf(stderr, "]");
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, " [options]");
+ }
+
+ fprintf(stderr, "\n\n");
+
+ if (phas_desc > 0) {
+ fprintf(stderr, "Where:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (entry->d == NULL) {
+ continue;
+ }
+
+ if (cli__entry_is_positional(entry)) {
+ fprintf(stderr, " %*.*s ",
+ (int)pmax_len,
+ (int)pmax_len,
+ entry->l);
+ cli__print_description(entry, pmax_len + 4);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, "Options:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+ const char *type_str;
+ size_t type_len;
+ size_t arg_len;
+
+ if (cli__entry_is_positional(entry)) {
+ continue;
+ }
+
+ if (entry->s != '\0') {
+ fprintf(stderr, " -%c", entry->s);
+ } else {
+ fprintf(stderr, " ");
+ }
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+ arg_len = strlen(entry->l);
+
+ fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
+ (int)(max_len - arg_len - type_len),
+ "");
+ cli__print_description(entry, max_len + 11);
+ fprintf(stderr, "\n");
+ }
+ }
+}
diff --git a/test/cli.h b/test/cli.h
new file mode 100644
index 0000000..91db086
--- /dev/null
+++ b/test/cli.h
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling API.
+ */
+
+#ifndef _PELTAR_CLI_H_
+#define _PELTAR_CLI_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Helper to get element count for an array,
+ *
+ * \param[in] _a Array to get number of elements for.
+ */
+#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
+
+/**
+ * CLI argument type.
+ */
+enum cli_arg_type {
+ CLI_CMD, /**< A sub-command. Must match long argument name. */
+ CLI_BOOL, /**< Has no value; presence of flag indicates true. */
+ CLI_INT, /**< Has signed integer value. */
+ CLI_UINT, /**< Has unsigned integer value. */
+ CLI_ENUM, /**< Has enumeration value. */
+ CLI_STRING, /**< Has string value. */
+};
+
+struct cli_str_val {
+ const char *str;
+ int64_t val;
+};
+
+struct cli_enum {
+ const struct cli_str_val *desc;
+ int64_t *e; /**< Location to store \ref CLI_ENUM value. */
+};
+
+/**
+ * Client description for a command line argument.
+ */
+struct cli_table_entry {
+ const char *l; /**< Long argument name. */
+ const char s; /**< Short flag name. (Non-positional arguments.) */
+ bool p; /**< Whether the argument is a positional argument. */
+ enum cli_arg_type t; /**< Argument type. */
+ union {
+ bool *b; /**< Location to store \ref CLI_BOOL value. */
+ int64_t *i; /**< Location to store \ref CLI_INT value. */
+ uint64_t *u; /**< Location to store \ref CLI_UINT value. */
+ const char **s; /**< Location to store \ref CLI_STRING value. */
+ struct cli_enum e;
+ } v; /**< Where to store type-specific values. */
+ const char *d; /**< Description. */
+};
+
+/**
+ * Client command line interface specification.
+ */
+struct cli_table {
+ const struct cli_table_entry *entries;
+ size_t count;
+ size_t min_positional;
+};
+
+/**
+ * Parse the command line arguments.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \return true on success, false on error.
+ */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
+
+/**
+ * Print usage and help output.
+ *
+ * Note: Assumes non-Unicode. (One byte per character.)
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] prog_name Program name.
+ */
+void cli_help(const struct cli_table *cli, const char *prog_name);
+
+#endif
diff --git a/test/decode_gif.c b/test/decode_gif.c
deleted file mode 100644
index dfc2b84..0000000
--- a/test/decode_gif.c
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#include "../include/libnsgif.h"
-
-#define BYTES_PER_PIXEL 4
-#define MAX_IMAGE_BYTES (48 * 1024 * 1024)
-
-static void *bitmap_create(int width, int height)
-{
- /* ensure a stupidly large bitmap is not created */
- if (((long long)width * (long long)height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) {
- return NULL;
- }
- return calloc(width * height, BYTES_PER_PIXEL);
-}
-
-static void bitmap_set_opaque(void *bitmap, bool opaque)
-{
- (void) opaque; /* unused */
- (void) bitmap; /* unused */
- assert(bitmap);
-}
-
-static bool bitmap_test_opaque(void *bitmap)
-{
- (void) bitmap; /* unused */
- assert(bitmap);
- return false;
-}
-
-static unsigned char *bitmap_get_buffer(void *bitmap)
-{
- assert(bitmap);
- return bitmap;
-}
-
-static void bitmap_destroy(void *bitmap)
-{
- assert(bitmap);
- free(bitmap);
-}
-
-static void bitmap_modified(void *bitmap)
-{
- (void) bitmap; /* unused */
- assert(bitmap);
- return;
-}
-
-static unsigned char *load_file(const char *path, size_t *data_size)
-{
- FILE *fd;
- struct stat sb;
- unsigned char *buffer;
- size_t size;
- size_t n;
-
- fd = fopen(path, "rb");
- if (!fd) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- if (stat(path, &sb)) {
- perror(path);
- exit(EXIT_FAILURE);
- }
- size = sb.st_size;
-
- buffer = malloc(size);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %lld bytes\n",
- (long long) size);
- exit(EXIT_FAILURE);
- }
-
- n = fread(buffer, 1, size, fd);
- if (n != size) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- fclose(fd);
-
- *data_size = size;
- return buffer;
-}
-
-static void warning(const char *context, gif_result code)
-{
- fprintf(stderr, "%s failed: ", context);
- switch (code)
- {
- case GIF_FRAME_DATA_ERROR:
- fprintf(stderr, "GIF_FRAME_DATA_ERROR");
- break;
- case GIF_INSUFFICIENT_DATA:
- fprintf(stderr, "GIF_INSUFFICIENT_DATA");
- break;
- case GIF_DATA_ERROR:
- fprintf(stderr, "GIF_DATA_ERROR");
- break;
- case GIF_INSUFFICIENT_MEMORY:
- fprintf(stderr, "GIF_INSUFFICIENT_MEMORY");
- break;
- default:
- fprintf(stderr, "unknown code %i", code);
- break;
- }
- fprintf(stderr, "\n");
-}
-
-static void write_ppm(FILE* fh, const char *name, gif_animation *gif,
- bool no_write)
-{
- unsigned int i;
- gif_result code;
-
- if (!no_write) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", gif->width);
- fprintf(fh, "# height %u \n", gif->height);
- fprintf(fh, "# frame_count %u \n", gif->frame_count);
- fprintf(fh, "# frame_count_partial %u \n", gif->frame_count_partial);
- fprintf(fh, "# loop_count %u \n", gif->loop_count);
- fprintf(fh, "%u %u 256\n", gif->width, gif->height * gif->frame_count);
- }
-
- /* decode the frames */
- for (i = 0; i != gif->frame_count; i++) {
- unsigned int row, col;
- unsigned char *image;
-
- code = gif_decode_frame(gif, i);
- 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;
- for (row = 0; row != gif->height; row++) {
- for (col = 0; col != gif->width; col++) {
- size_t z = (row * gif->width + col) * 4;
- fprintf(fh, "%u %u %u ",
- (unsigned char) image[z],
- (unsigned char) image[z + 1],
- (unsigned char) image[z + 2]);
- }
- fprintf(fh, "\n");
- }
- }
- }
-}
-
-int main(int argc, char *argv[])
-{
- gif_bitmap_callback_vt bitmap_callbacks = {
- bitmap_create,
- bitmap_destroy,
- bitmap_get_buffer,
- bitmap_set_opaque,
- bitmap_test_opaque,
- bitmap_modified
- };
- gif_animation gif;
- size_t size;
- gif_result code;
- unsigned char *data;
- FILE *outf = stdout;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
- }
-
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
- }
- }
-
- /* create our gif animation */
- gif_create(&gif, &bitmap_callbacks);
-
- /* load file into memory */
- data = load_file(argv[1], &size);
-
- /* begin decoding */
- do {
- code = gif_initialise(&gif, size, data);
- if (code != GIF_OK && code != GIF_WORKING) {
- warning("gif_initialise", code);
- gif_finalise(&gif);
- free(data);
- return 1;
- }
- } while (code != GIF_OK);
-
- write_ppm(outf, argv[1], &gif, no_write);
-
- if (argc > 2 && !no_write) {
- fclose(outf);
- }
-
- /* clean up */
- gif_finalise(&gif);
- free(data);
-
- return 0;
-}
diff --git a/test/nsgif.c b/test/nsgif.c
new file mode 100644
index 0000000..b1b2424
--- /dev/null
+++ b/test/nsgif.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
+ * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
+ * Copyright 2022 Michael Drake <tlsa(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "../include/nsgif.h"
+
+#include "cli.h"
+#include "cli.c"
+
+#define BYTES_PER_PIXEL 4
+
+static struct nsgif_options {
+ const char *file;
+ const char *ppm;
+ uint64_t loops;
+ bool info;
+} nsgif_options;
+
+static const struct cli_table_entry cli_entries[] = {
+ {
+ .s = 'm',
+ .l = "ppm",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.ppm,
+ .d = "Convert frames to PPM image at given path."
+ },
+ {
+ .s = 'i',
+ .l = "info",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.info,
+ .d = "Dump GIF info to stdout."
+ },
+ {
+ .s = 'l',
+ .l = "loops",
+ .t = CLI_UINT,
+ .v.u = &nsgif_options.loops,
+ .d = "Loop through decoding all frames N times. "
+ "The default is 1."
+ },
+ {
+ .p = true,
+ .l = "FILE",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.file,
+ .d = "Path to GIF file to load."
+ },
+};
+
+const struct cli_table cli = {
+ .entries = cli_entries,
+ .count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
+ .min_positional = 1,
+};
+
+static void *bitmap_create(int width, int height)
+{
+ /* Ensure a stupidly large bitmap is not created */
+ if (width > 4096 || height > 4096) {
+ return NULL;
+ }
+
+ return calloc(width * height, BYTES_PER_PIXEL);
+}
+
+static unsigned char *bitmap_get_buffer(void *bitmap)
+{
+ return bitmap;
+}
+
+static void bitmap_destroy(void *bitmap)
+{
+ free(bitmap);
+}
+
+static uint8_t *load_file(const char *path, size_t *data_size)
+{
+ FILE *fd;
+ struct stat sb;
+ unsigned char *buffer;
+ size_t size;
+ size_t n;
+
+ fd = fopen(path, "rb");
+ if (!fd) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(path, &sb)) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+ size = sb.st_size;
+
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %lld bytes\n",
+ (long long) size);
+ exit(EXIT_FAILURE);
+ }
+
+ n = fread(buffer, 1, size, fd);
+ if (n != size) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ fclose(fd);
+
+ *data_size = size;
+ return buffer;
+}
+
+static void warning(const char *context, nsgif_error err)
+{
+ fprintf(stderr, "%s failed: %s\n",
+ context, nsgif_strerror(err));
+}
+
+static void print_gif_info(const nsgif_info_t *info)
+{
+ fprintf(stdout, "gif:\n");
+ fprintf(stdout, " width: %"PRIu32"\n", info->width);
+ fprintf(stdout, " height: %"PRIu32"\n", info->height);
+ fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
+ fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
+ fprintf(stdout, " frames:\n");
+}
+
+static void print_gif_frame_info(const nsgif_frame_info_t *info)
+{
+ const char *disposal = nsgif_str_disposal(info->disposal);
+
+ fprintf(stdout, " - disposal-method: %s\n", disposal);
+ fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
+ fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
+ fprintf(stdout, " rect:\n");
+ fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
+ fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
+ fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
+ fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
+}
+
+static void decode(FILE* ppm, const char *name, nsgif_t *gif)
+{
+ nsgif_error err;
+ uint32_t frame_prev = 0;
+ const nsgif_info_t *info;
+
+ info = nsgif_get_info(gif);
+
+ if (ppm != NULL) {
+ fprintf(ppm, "P3\n");
+ fprintf(ppm, "# %s\n", name);
+ fprintf(ppm, "# width %u \n", info->width);
+ fprintf(ppm, "# height %u \n", info->height);
+ fprintf(ppm, "# frame_count %u \n", info->frame_count);
+ fprintf(ppm, "# loop_max %u \n", info->loop_max);
+ fprintf(ppm, "%u %u 256\n", info->width,
+ info->height * info->frame_count);
+ }
+
+ if (nsgif_options.info == true) {
+ print_gif_info(info);
+ }
+
+ /* decode the frames */
+ while (true) {
+ nsgif_bitmap_t *bitmap;
+ const uint8_t *image;
+ uint32_t frame_new;
+ uint32_t delay_cs;
+ nsgif_rect_t area;
+
+ err = nsgif_frame_prepare(gif, &area,
+ &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ warning("nsgif_frame_prepare", err);
+ return;
+ }
+
+ if (frame_new < frame_prev) {
+ /* Must be an animation that loops. We only care about
+ * decoding each frame once in this utility. */
+ return;
+ }
+ frame_prev = frame_new;
+
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
+ if (err != NSGIF_OK) {
+ warning("nsgif_decode_frame", err);
+ return;
+ }
+
+ if (nsgif_options.info == true) {
+ const nsgif_frame_info_t *f_info;
+
+ f_info = nsgif_get_frame_info(gif, frame_new);
+ assert(f_info != NULL);
+ print_gif_frame_info(f_info);
+ }
+
+ if (ppm != NULL) {
+ fprintf(ppm, "# frame %u:\n", frame_new);
+ image = (const uint8_t *) bitmap;
+ for (uint32_t y = 0; y != info->height; y++) {
+ for (uint32_t x = 0; x != info->width; x++) {
+ size_t z = (y * info->width + x) * 4;
+ fprintf(ppm, "%u %u %u ",
+ image[z],
+ image[z + 1],
+ image[z + 2]);
+ }
+ fprintf(ppm, "\n");
+ }
+ }
+
+ if (delay_cs == NSGIF_INFINITE) {
+ /** This frame is the last. */
+ return;
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const nsgif_bitmap_cb_vt bitmap_callbacks = {
+ .create = bitmap_create,
+ .destroy = bitmap_destroy,
+ .get_buffer = bitmap_get_buffer,
+ };
+ size_t size;
+ nsgif_t *gif;
+ uint8_t *data;
+ nsgif_error err;
+ FILE *ppm = NULL;
+
+ /* Override default options with any command line args */
+ if (!cli_parse(&cli, argc, (void *)argv)) {
+ cli_help(&cli, argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (nsgif_options.ppm != NULL) {
+ ppm = fopen(nsgif_options.ppm, "w+");
+ if (ppm == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ nsgif_options.ppm);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* create our gif animation */
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ warning("nsgif_create", err);
+ return EXIT_FAILURE;
+ }
+
+ /* load file into memory */
+ data = load_file(nsgif_options.file, &size);
+
+ /* Scan the raw data */
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ warning("nsgif_data_scan", err);
+ nsgif_destroy(gif);
+ free(data);
+ return EXIT_FAILURE;
+ }
+
+ for (uint64_t i = 0; i < nsgif_options.loops; i++) {
+ decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
+ }
+
+ if (ppm != NULL) {
+ fclose(ppm);
+ }
+
+ /* clean up */
+ nsgif_destroy(gif);
+ free(data);
+
+ return 0;
+}
diff --git a/test/runtest.sh b/test/runtest.sh
index 06eaef4..fd84847 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh
# run test images through libnsgif and count results
@@ -20,25 +20,25 @@ GIFTESTS="${GIFTESTS} test/ns-afl-gif/*.gif"
gifdecode()
{
- OUTF=$(basename ${1} .gif)
- CMPF=$(dirname ${1})/${OUTF}.ppm
- echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_decode_gif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
- ECODE=$?
-
- echo "Exit code:${ECODE}" >> ${TEST_LOG}
- if [ "${ECODE}" -gt 0 ];then
- return ${ECODE}
- fi
-
- if [ -f "${CMPF}" ]; then
- cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
- if [ "$?" -ne 0 ]; then
- return 128
+ OUTF=$(basename ${1} .gif)
+ CMPF=$(dirname ${1})/${OUTF}.ppm
+ echo "GIF:${1}" >> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} --ppm ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ECODE=$?
+
+ echo "Exit code:${ECODE}" >> ${TEST_LOG}
+ if [ "${ECODE}" -gt 0 ];then
+ return ${ECODE}
fi
- fi
- return 0
+ if [ -f "${CMPF}" ]; then
+ cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
+ if [ "$?" -ne 0 ]; then
+ return 128
+ fi
+ fi
+
+ return 0
}
GIFTESTTOTC=0
@@ -49,26 +49,27 @@ GIFTESTERRC=0
echo "Testing GIF decode"
for GIF in $(ls ${GIFTESTS});do
- GIFTESTTOTC=$((GIFTESTTOTC+1))
- gifdecode ${GIF}
- ECODE=$?
- if [ "${ECODE}" -gt 127 ];then
- GIFTESTERRC=$((GIFTESTERRC+1))
- echo "Error ${GIF}"
- else
- if [ "${ECODE}" -gt 0 ];then
- GIFTESTFAILC=$((GIFTESTFAILC+1))
+ GIFTESTTOTC=$((GIFTESTTOTC+1))
+ #echo "${GIF}"
+ gifdecode ${GIF}
+ ECODE=$?
+ if [ "${ECODE}" -gt 127 ];then
+ GIFTESTERRC=$((GIFTESTERRC+1))
+ echo "Error ${GIF}"
else
- GIFTESTPASSC=$((GIFTESTPASSC+1))
+ if [ "${ECODE}" -gt 0 ];then
+ GIFTESTFAILC=$((GIFTESTFAILC+1))
+ else
+ GIFTESTPASSC=$((GIFTESTPASSC+1))
+ fi
fi
- fi
done
echo "Tests:${GIFTESTTOTC} Pass:${GIFTESTPASSC} Fail:${GIFTESTFAILC} Error:${GIFTESTERRC}"
# exit code
if [ "${GIFTESTERRC}" -gt 0 ]; then
- exit 1
+ exit 1
fi
exit 0
--
NetSurf GIF Decoder
1 year, 7 months
libnsgif: branch tlsa/gif-api updated. release/0.2.1-152-g41d8bcf
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/41d8bcf828af44c77545...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/41d8bcf828af44c775454e...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/41d8bcf828af44c775454e46...
The branch, tlsa/gif-api has been updated
discards 4a037e58acc229ba133e727b15b3fd3d1d3b0c5b (commit)
discards 02b1c6de36df8f4cd0b1eea52dafbd8840756f95 (commit)
discards c88d94dfa6b4bb17a2d9855feb686025d1eb5be7 (commit)
discards fe85229b8eb2ec6a160e8dd8bf44a3d563b53c1e (commit)
via 41d8bcf828af44c775454e46ce751f9ac1a3684e (commit)
via 7ee51c0f6025238640cfa55faa36cd73e12489c7 (commit)
via a0025eda79e5f6b7f0ae23e7a85fd947dc847726 (commit)
via bc358088b2b074f09e638f101d2a12b3632a0d73 (commit)
via 4360a768898cc7a43715926e2e8040cb9748bead (commit)
via c800fec625521c9cb791d69933c8084e390c2bd0 (commit)
via ba2037410035d5b95e7458576654f8122ec581ac (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (4a037e58acc229ba133e727b15b3fd3d1d3b0c5b)
\
N -- N -- N (41d8bcf828af44c775454e46ce751f9ac1a3684e)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
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=41d8bcf828af44c775...
commit 41d8bcf828af44c775454e46ce751f9ac1a3684e
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Docs: Update README for new API.
diff --git a/README.md b/README.md
index 498ee46..d821ac6 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,116 @@
-libnsgif - Decoding GIF files
+LibNSGIF: NetSurf GIF decoder
=============================
-The functions provided by this library allow for efficient progressive
-GIF decoding. Whilst the initialisation does not ensure that there is
-sufficient image data to complete the entire frame, it does ensure
-that the information provided is valid. Any subsequent attempts to
-decode an initialised GIF are guaranteed to succeed, and any bytes of
-the image not present are assumed to be totally transparent.
-
-To begin decoding a GIF, the 'gif' structure must be initialised with
-the 'gif_data' and 'buffer_size' set to their initial values. The
-'buffer_position' should initially be 0, and will be internally
-updated as the decoding commences. The caller should then repeatedly
-call gif_initialise() with the structure until the function returns 1,
-or no more data is avaliable.
-
-Once the initialisation has begun, the decoder completes the variables
-'frame_count' and 'frame_count_partial'. The former being the total
-number of frames that have been successfully initialised, and the
-latter being the number of frames that a partial amount of data is
-available for. This assists the caller in managing the animation
-whilst decoding is continuing.
-
-To decode a frame, the caller must use gif_decode_frame() which
-updates the current 'frame_image' to reflect the desired frame. The
-required 'disposal_method' is also updated to reflect how the frame
-should be plotted. The caller must not assume that the current
-'frame_image' will be valid between calls if initialisation is still
-occuring, and should either always request that the frame is decoded
-(no processing will occur if the 'decoded_frame' has not been
-invalidated by initialisation) or perform the check itself.
-
-It should be noted that gif_finalise() should always be called, even
-if no frames were initialised. Additionally, it is the responsibility
-of the caller to free 'gif_data'.
+LibNSGIF is a C library for decoding GIF format images and animations.
+It is licenced under the MIT licence.
+
+This library aims to provide a simple API for robust decoding of GIF files.
+
+Details
+-------
+
+The GIF source data is scanned prior to decoding, allowing for efficient
+decoding. The scanning phase will scan currently available data and will
+resume from where it left off when called with additional data.
+
+Only one frame is ever fully decoded to a bitmap at a time, reducing memory
+usage for large GIFs.
+
+Using
+-----
+
+LibNSGIF allows the client to allocate the bitmap into which the GIF is
+decoded. The client can have an arbitrary bitmap structure, that is simply
+a void pointer to LibNSGIF. The client must provide a callback table for
+interacting with bitmaps. This table must include as a minimum functions to
+create and destroy bitmaps, and a function to get a pointer to the bitmap's
+pixel data buffer.
+
+To load a GIF, first create an nsgif object with `nsgif_create()`.
+
+```c
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Now you can load the GIF source data into the nsgif object with
+`nsgif_data_scan()`:
+
+```c
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+This scans the source data and decodes information about each frame, however
+it doesn't decode any of the bitmap data for the frames. The client may call
+`nsgif_data_scan()` multiple times as source data is fetched. Once the
+function has returned `NSGIF_OK` it has enough data to display at least one
+frame. The early frames can be decoded before the later frames are scanned.
+Frames have to be scanned before they can be decoded.
+
+To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
+and then call `nsgif_frame_decode()` for each frame, and manage the animation,
+and non-displayable frames yourself, or you can use the helper function,
+`nsgif_frame_prepare()`:
+
+```c
+ err = nsgif_frame_prepare(gif, &area, &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+
+ // Update our bitmap to know it should be showing `frame_new` now.
+ // Trigger redraw of `area` of image.
+
+ if (delay_cs != NSGIF_INFINITE) {
+ // Schedule next frame in delay_cs.
+ }
+```
+
+This will return the number of the next frame to be decoded, the delay in cs
+before the next frame should be decoded, and the area of the bitmap that needs
+to be redrawn.
+
+> **Note**: GIF frames may only occupy a portion of the overall bitmap, and only
+> redrawing the area that has changed may be more efficient than redrawing the
+> whole thing. The returned area comprises both any region that has been
+> changed in the disposal of the previous frame and the new frame.
+
+GIF files can limit the number of animation loops to a finite number or they
+may only have one frame. In either of these cases, the returned delay is
+`NSGIF_INFINITE` indicating that the animation is complete. Subsequent calls
+to `nsgif_frame_prepare()` will return `NSGIF_ERR_ANIMATION_END`.
+
+To force the repeat of an animation, call `nsgif_reset()`.
+
+One reason for the two-step decoding of frames is that it enables deferred
+decoding. You can call `nsgif_frame_prepare()` and cause a redraw of that
+portion of your document. If the GIF is off screen (another tab, or scrolled
+out of sight), there is no need to decode it at all.
+
+Once the bitmap is needed for a redraw, you can decode the correct frame
+on-demand with:
+
+```c
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Note that this will be a no-op if the requested frame already happens to be
+the decoded frame.
+
+Once you are done with the GIF, free up the nsgif object with:
+
+```c
+ nsgif_destroy(gif);
+```
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=7ee51c0f6025238640...
commit 7ee51c0f6025238640cfa55faa36cd73e12489c7
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Docs: Rename readme to have markdown extension.
diff --git a/README b/README
deleted file mode 100644
index 498ee46..0000000
--- a/README
+++ /dev/null
@@ -1,36 +0,0 @@
-libnsgif - Decoding GIF files
-=============================
-
-The functions provided by this library allow for efficient progressive
-GIF decoding. Whilst the initialisation does not ensure that there is
-sufficient image data to complete the entire frame, it does ensure
-that the information provided is valid. Any subsequent attempts to
-decode an initialised GIF are guaranteed to succeed, and any bytes of
-the image not present are assumed to be totally transparent.
-
-To begin decoding a GIF, the 'gif' structure must be initialised with
-the 'gif_data' and 'buffer_size' set to their initial values. The
-'buffer_position' should initially be 0, and will be internally
-updated as the decoding commences. The caller should then repeatedly
-call gif_initialise() with the structure until the function returns 1,
-or no more data is avaliable.
-
-Once the initialisation has begun, the decoder completes the variables
-'frame_count' and 'frame_count_partial'. The former being the total
-number of frames that have been successfully initialised, and the
-latter being the number of frames that a partial amount of data is
-available for. This assists the caller in managing the animation
-whilst decoding is continuing.
-
-To decode a frame, the caller must use gif_decode_frame() which
-updates the current 'frame_image' to reflect the desired frame. The
-required 'disposal_method' is also updated to reflect how the frame
-should be plotted. The caller must not assume that the current
-'frame_image' will be valid between calls if initialisation is still
-occuring, and should either always request that the frame is decoded
-(no processing will occur if the 'decoded_frame' has not been
-invalidated by initialisation) or perform the check itself.
-
-It should be noted that gif_finalise() should always be called, even
-if no frames were initialised. Additionally, it is the responsibility
-of the caller to free 'gif_data'.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..498ee46
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+libnsgif - Decoding GIF files
+=============================
+
+The functions provided by this library allow for efficient progressive
+GIF decoding. Whilst the initialisation does not ensure that there is
+sufficient image data to complete the entire frame, it does ensure
+that the information provided is valid. Any subsequent attempts to
+decode an initialised GIF are guaranteed to succeed, and any bytes of
+the image not present are assumed to be totally transparent.
+
+To begin decoding a GIF, the 'gif' structure must be initialised with
+the 'gif_data' and 'buffer_size' set to their initial values. The
+'buffer_position' should initially be 0, and will be internally
+updated as the decoding commences. The caller should then repeatedly
+call gif_initialise() with the structure until the function returns 1,
+or no more data is avaliable.
+
+Once the initialisation has begun, the decoder completes the variables
+'frame_count' and 'frame_count_partial'. The former being the total
+number of frames that have been successfully initialised, and the
+latter being the number of frames that a partial amount of data is
+available for. This assists the caller in managing the animation
+whilst decoding is continuing.
+
+To decode a frame, the caller must use gif_decode_frame() which
+updates the current 'frame_image' to reflect the desired frame. The
+required 'disposal_method' is also updated to reflect how the frame
+should be plotted. The caller must not assume that the current
+'frame_image' will be valid between calls if initialisation is still
+occuring, and should either always request that the frame is decoded
+(no processing will occur if the 'decoded_frame' has not been
+invalidated by initialisation) or perform the check itself.
+
+It should be noted that gif_finalise() should always be called, even
+if no frames were initialised. Additionally, it is the responsibility
+of the caller to free 'gif_data'.
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=a0025eda79e5f6b7f0...
commit a0025eda79e5f6b7f0ae23e7a85fd947dc847726
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Source: Update copyright years.
diff --git a/include/nsgif.h b/include/nsgif.h
index 2c8a60c..b60747f 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -1,7 +1,7 @@
/*
* Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
diff --git a/src/gif.c b/src/gif.c
index 5317a9e..467ff90 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1,7 +1,7 @@
/*
* Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
diff --git a/test/nsgif.c b/test/nsgif.c
index 50b75b0..b1b2424 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -1,6 +1,7 @@
/*
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
* Copyright 2008 James Bursa <james(a)netsurf-browser.org>
+ * Copyright 2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=bc358088b2b074f09e...
commit bc358088b2b074f09e638f101d2a12b3632a0d73
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Simplify frame delay calculation.
diff --git a/src/gif.c b/src/gif.c
index db99afd..5317a9e 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1618,7 +1618,6 @@ nsgif_error nsgif_frame_prepare(
};
uint32_t delay = 0;
uint32_t frame = gif->frame;
- uint32_t frame_next;
if (gif->frame != NSGIF_FRAME_INVALID &&
gif->frame < gif->info.frame_count &&
@@ -1632,7 +1631,7 @@ nsgif_error nsgif_frame_prepare(
return NSGIF_ERR_ANIMATION_END;
}
- ret = nsgif__next_displayable_frame(gif, &frame, NULL);
+ ret = nsgif__next_displayable_frame(gif, &frame, &delay);
if (ret != NSGIF_OK) {
return ret;
}
@@ -1641,17 +1640,22 @@ nsgif_error nsgif_frame_prepare(
gif->info.loop_count++;
}
- frame_next = frame;
- ret = nsgif__next_displayable_frame(gif, &frame_next, &delay);
- if (ret != NSGIF_OK) {
- return ret;
- }
+ if (gif->info.frame_count == 1) {
+ delay = NSGIF_INFINITE;
+
+ } else if (gif->info.loop_max != 0) {
+ uint32_t frame_next = frame;
+ ret = nsgif__next_displayable_frame(gif, &frame_next, NULL);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
- if (frame_next < frame) {
- if (nsgif__animation_complete(
- gif->info.loop_count + 1,
- gif->info.loop_max)) {
- delay = NSGIF_INFINITE;
+ if (frame_next < frame) {
+ if (nsgif__animation_complete(
+ gif->info.loop_count + 1,
+ gif->info.loop_max)) {
+ delay = NSGIF_INFINITE;
+ }
}
}
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=4360a768898cc7a437...
commit 4360a768898cc7a43715926e2e8040cb9748bead
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Don't error for a final partial frame.
diff --git a/src/gif.c b/src/gif.c
index 3eebbd6..db99afd 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1517,9 +1517,8 @@ nsgif_error nsgif_data_scan(
ret = nsgif__process_frame(gif, frames, false);
} while (gif->info.frame_count > frames);
- if (ret == NSGIF_ERR_END_OF_DATA &&
- gif->info.frame_count > 0) {
- ret = NSGIF_ERR_END_OF_FRAME;
+ if (ret == NSGIF_ERR_END_OF_DATA && gif->info.frame_count > 0) {
+ ret = NSGIF_OK;
}
return ret;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=c800fec625521c9cb7...
commit c800fec625521c9cb791d69933c8084e390c2bd0
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
API: Consistent typedef naming convention.
diff --git a/include/nsgif.h b/include/nsgif.h
index b36af8a..2c8a60c 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -23,7 +23,7 @@
/** Representation of infinity. */
#define NSGIF_INFINITE (UINT32_MAX)
-typedef struct nsgif nsgif;
+typedef struct nsgif nsgif_t;
/**
* GIF rectangle structure.
@@ -42,7 +42,7 @@ typedef struct nsgif_rect {
uint32_t x1;
/** y co-ordinate of redraw rectangle, bottom */
uint32_t y1;
-} nsgif_rect;
+} nsgif_rect_t;
/**
* NSGIF return codes.
@@ -178,14 +178,16 @@ const char *nsgif_strerror(nsgif_error err);
*
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
+nsgif_error nsgif_create(
+ const nsgif_bitmap_cb_vt *bitmap_vt,
+ nsgif_t **gif_out);
/**
* Free a NSGIF object.
*
* \param[in] gif The NSGIF to free.
*/
-void nsgif_destroy(nsgif *gif);
+void nsgif_destroy(nsgif_t *gif);
/**
* Scan the source image data.
@@ -212,7 +214,7 @@ void nsgif_destroy(nsgif *gif);
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_data_scan(
- nsgif *gif,
+ nsgif_t *gif,
size_t size,
const uint8_t *data);
@@ -231,8 +233,8 @@ nsgif_error nsgif_data_scan(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new);
@@ -247,7 +249,7 @@ nsgif_error nsgif_frame_prepare(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_frame_decode(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap);
@@ -264,7 +266,7 @@ nsgif_error nsgif_frame_decode(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_reset(
- nsgif *gif);
+ nsgif_t *gif);
/**
* Information about a GIF.
@@ -317,7 +319,7 @@ typedef struct nsgif_frame_info {
uint32_t delay;
/** Frame's redraw rectangle. */
- nsgif_rect rect;
+ nsgif_rect_t rect;
} nsgif_frame_info_t;
/**
@@ -327,7 +329,7 @@ typedef struct nsgif_frame_info {
*
* \return The gif info, or NULL on error.
*/
-const nsgif_info_t *nsgif_get_info(const nsgif *gif);
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
/**
* Get information about a GIF from an NSGIF object.
@@ -338,7 +340,7 @@ const nsgif_info_t *nsgif_get_info(const nsgif *gif);
* \return The gif frame info, or NULL on error.
*/
const nsgif_frame_info_t *nsgif_get_frame_info(
- const nsgif *gif,
+ const nsgif_t *gif,
uint32_t frame);
#endif
diff --git a/src/gif.c b/src/gif.c
index a5f059e..3eebbd6 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1257,7 +1257,7 @@ cleanup:
}
/* exported function documented in nsgif.h */
-void nsgif_destroy(nsgif *gif)
+void nsgif_destroy(nsgif_t *gif)
{
if (gif == NULL) {
return;
@@ -1283,9 +1283,9 @@ void nsgif_destroy(nsgif *gif)
}
/* exported function documented in nsgif.h */
-nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
{
- nsgif *gif;
+ nsgif_t *gif;
gif = calloc(1, sizeof(*gif));
if (gif == NULL) {
@@ -1389,7 +1389,7 @@ static nsgif_error nsgif__parse_logical_screen_descriptor(
/* exported function documented in nsgif.h */
nsgif_error nsgif_data_scan(
- nsgif *gif,
+ nsgif_t *gif,
size_t size,
const uint8_t *data)
{
@@ -1525,7 +1525,9 @@ nsgif_error nsgif_data_scan(
return ret;
}
-static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redraw)
+static void nsgif__redraw_rect_extend(
+ const nsgif_rect_t *frame,
+ nsgif_rect_t *redraw)
{
if (redraw->x1 == 0 || redraw->y1 == 0) {
*redraw = *frame;
@@ -1546,7 +1548,7 @@ static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redra
}
static uint32_t nsgif__frame_next(
- nsgif *gif,
+ nsgif_t *gif,
bool partial,
uint32_t frame)
{
@@ -1563,7 +1565,7 @@ static uint32_t nsgif__frame_next(
}
static nsgif_error nsgif__next_displayable_frame(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t *frame,
uint32_t *delay)
{
@@ -1595,7 +1597,7 @@ static inline bool nsgif__animation_complete(int count, int max)
}
nsgif_error nsgif_reset(
- nsgif *gif)
+ nsgif_t *gif)
{
gif->info.loop_count = 0;
gif->frame = NSGIF_FRAME_INVALID;
@@ -1605,13 +1607,13 @@ nsgif_error nsgif_reset(
/* exported function documented in nsgif.h */
nsgif_error nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new)
{
nsgif_error ret;
- nsgif_rect rect = {
+ nsgif_rect_t rect = {
.x1 = 0,
.y1 = 0,
};
@@ -1666,7 +1668,7 @@ nsgif_error nsgif_frame_prepare(
/* exported function documented in nsgif.h */
nsgif_error nsgif_frame_decode(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap)
{
@@ -1702,14 +1704,14 @@ nsgif_error nsgif_frame_decode(
}
/* exported function documented in nsgif.h */
-const nsgif_info_t *nsgif_get_info(const nsgif *gif)
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif)
{
return &gif->info;
}
/* exported function documented in nsgif.h */
const nsgif_frame_info_t *nsgif_get_frame_info(
- const nsgif *gif,
+ const nsgif_t *gif,
uint32_t frame)
{
if (frame > gif->info.frame_count) {
diff --git a/test/nsgif.c b/test/nsgif.c
index 23fd3f3..50b75b0 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -156,7 +156,7 @@ static void print_gif_frame_info(const nsgif_frame_info_t *info)
fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
}
-static void decode(FILE* ppm, const char *name, nsgif *gif)
+static void decode(FILE* ppm, const char *name, nsgif_t *gif)
{
nsgif_error err;
uint32_t frame_prev = 0;
@@ -185,7 +185,7 @@ static void decode(FILE* ppm, const char *name, nsgif *gif)
const uint8_t *image;
uint32_t frame_new;
uint32_t delay_cs;
- nsgif_rect area;
+ nsgif_rect_t area;
err = nsgif_frame_prepare(gif, &area,
&delay_cs, &frame_new);
@@ -244,8 +244,8 @@ int main(int argc, char *argv[])
.destroy = bitmap_destroy,
.get_buffer = bitmap_get_buffer,
};
- nsgif *gif;
size_t size;
+ nsgif_t *gif;
uint8_t *data;
nsgif_error err;
FILE *ppm = NULL;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=ba2037410035d5b95e...
commit ba2037410035d5b95e7458576654f8122ec581ac
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Test: Add support for gif info dump and loop count to nsgif tool.
diff --git a/test/cli.c b/test/cli.c
new file mode 100644
index 0000000..031cd97
--- /dev/null
+++ b/test/cli.c
@@ -0,0 +1,763 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cli.h"
+
+/**
+ * Check whether a CLI argument type should have a numerical value.
+ *
+ * \param[in] type An argument type.
+ * \return true if the argument needs a numerical value, or false otherwise.
+ */
+static inline bool cli__arg_is_numerical(enum cli_arg_type type)
+{
+ return (type != CLI_STRING && type != CLI_BOOL);
+}
+
+/**
+ * Parse a signed integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] i Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_int(
+ const char *str,
+ int64_t *i,
+ size_t *pos)
+{
+ long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoll(str, &end, 0);
+
+ if (end == str || errno == ERANGE ||
+ temp > INT64_MAX || temp < INT64_MIN) {
+ fprintf(stderr, "Failed to parse integer from '%s'\n", str);
+ return false;
+ }
+
+ *i = (int64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an unsigned integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] u Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_uint(
+ const char *str,
+ uint64_t *u,
+ size_t *pos)
+{
+ unsigned long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoull(str, &end, 0);
+
+ if (end == str || errno == ERANGE || temp > UINT64_MAX) {
+ fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
+ return false;
+ }
+
+ *u = (uint64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an enum value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] e Enum details.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_enum(
+ const char *str,
+ const struct cli_enum *e,
+ size_t *pos)
+{
+ str += *pos;
+ *pos += strlen(str);
+
+ for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
+ if (strcmp(str, sv->str) == 0) {
+ *e->e = sv->val;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Parse a string value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] s Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_string(
+ const char *str,
+ const char **s,
+ size_t *pos)
+{
+ *s = str + *pos;
+ *pos += strlen(*s);
+ return true;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse a value from.
+ * \param[in,out] pos Current position in argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value(
+ const struct cli_table_entry *entry,
+ const char *arg,
+ size_t *pos)
+{
+ switch (entry->t) {
+ case CLI_CMD:
+ if (strcmp(arg + *pos, entry->l) == 0) {
+ *pos += strlen(arg);
+ return true;
+ }
+ return false;
+
+ case CLI_INT:
+ return cli__parse_value_int(arg, entry->v.i, pos);
+
+ case CLI_UINT:
+ return cli__parse_value_uint(arg, entry->v.u, pos);
+
+ case CLI_ENUM:
+ return cli__parse_value_enum(arg, &entry->v.e, pos);
+
+ case CLI_STRING:
+ return cli__parse_value_string(arg, entry->v.s, pos);
+
+ default:
+ fprintf(stderr, "Unexpected value for '%s': %s\n",
+ entry->l, arg);
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in] arg_pos Current position in argv.
+ * \param[in,out] pos Current pos in current argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_argv_value(const struct cli_table_entry *entry,
+ int argc, const char **argv,
+ int arg_pos, size_t *pos)
+{
+ const char *arg = argv[arg_pos];
+
+ if (arg_pos >= argc) {
+ fprintf(stderr, "Value not given for '%s'\n", entry->l);
+ return false;
+ }
+
+ return cli__parse_value(entry, arg, pos);
+}
+
+/**
+ * Check whether a CLI argument is a positional value.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \return true if the argument is positional, or false otherwise.
+ */
+static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
+{
+ return entry->p;
+}
+
+/**
+ * Look up a short argument flag.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] s Argument flag to look up in client CLI spec.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_short(
+ const struct cli_table *cli, char s)
+{
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ continue;
+ }
+ if (cli->entries[i].s == s) {
+ return &cli->entries[i];
+ }
+ }
+
+ fprintf(stderr, "Unknown flag: '%c'\n", s);
+ return NULL;
+}
+
+/**
+ * Handle an argument with a type that requires a value.
+ *
+ * This can handle the value being in the current argument, optionally split by
+ * a separator, or in the next argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in,out] arg_pos Current position in argv, updated on exit.
+ * \param[in] pos Current position in current argument string.
+ * \param[in] sep Name/value separator character, or '\0' if none.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__handle_arg_value(const struct cli_table_entry *entry,
+ int argc, const char **argv, int *arg_pos, size_t pos, char sep)
+{
+ const char *arg = argv[*arg_pos];
+ size_t orig_pos;
+ bool ret;
+
+ if (arg[pos] == '\0') {
+ (*arg_pos)++;
+ pos = 0;
+ } else if (arg[pos] == sep) {
+ pos++;
+ } else if (cli__arg_is_numerical(entry->t) == false) {
+ fprintf(stderr, "Separator required for non-numerical value\n");
+ return false;
+ }
+
+ if (isspace(argv[*arg_pos][pos])) {
+ fprintf(stderr, "Unexpected white space in '%s' "
+ "for argument '%s'\n",
+ &argv[*arg_pos][pos], entry->l);
+ return false;
+ }
+
+ orig_pos = pos;
+ ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
+ if (ret != true) {
+ return ret;
+ }
+
+ if (argv[*arg_pos][pos] != '\0') {
+ fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
+ &argv[*arg_pos][orig_pos], entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a flags argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_short(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const char *arg = argv[*arg_pos];
+ size_t pos = 1;
+
+ if (arg[0] != '-') {
+ return false;
+ }
+
+ while (arg[pos] != '\0') {
+ const struct cli_table_entry *entry;
+
+ entry = cli__lookup_short(cli, arg[pos]);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ *entry->v.b = true;
+ } else {
+ return cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos + 1, '\0');
+ }
+
+ pos++;
+ }
+
+ return true;
+}
+
+/**
+ * Look up a long argument name.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument name to look up in cli spec.
+ * \param[in,out] pos Current position in arg, updated on exit.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_long(
+ const struct cli_table *cli,
+ const char *arg,
+ size_t *pos)
+{
+ arg += *pos;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i]) == false) {
+ const char *name = cli->entries[i].l;
+ size_t name_len = strlen(cli->entries[i].l);
+
+ if (strncmp(name, arg, name_len) == 0) {
+ if (arg[name_len] != '\0' &&
+ arg[name_len] != '=') {
+ continue;
+ }
+ *pos += name_len;
+ return &cli->entries[i];
+ }
+ }
+ }
+
+ fprintf(stderr, "Unknown argument: '%s'\n", arg);
+ return NULL;
+}
+
+/**
+ * Parse a long argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_long(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const struct cli_table_entry *entry;
+ const char *arg = argv[*arg_pos];
+ size_t pos = 2;
+
+ if (arg[0] != '-' ||
+ arg[1] != '-') {
+ return false;
+ }
+
+ entry = cli__lookup_long(cli, arg, &pos);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ if (arg[pos] != '\0') {
+ fprintf(stderr, "Unexpected value for argument '%s'\n",
+ arg);
+ return false;
+ }
+ *entry->v.b = true;
+ } else {
+ bool ret;
+
+ ret = cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos, '=');
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument according to the given CLI spec entry.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional_entry(
+ const struct cli_table_entry *entry,
+ const char *arg)
+{
+ size_t pos = 0;
+ bool ret;
+
+ ret = cli__parse_value(entry, arg, &pos);
+ if (ret != true) {
+ return ret;
+ } else if (arg[pos] != '\0') {
+ fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
+ arg, entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument to parse.
+ * \param[in] count Number of positional arguments parsed already.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional(const struct cli_table *cli,
+ const char *arg, size_t count)
+{
+ size_t positional = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ if (positional == count) {
+ return cli__parse_positional_entry(
+ &cli->entries[i], arg);
+ }
+
+ positional++;
+ }
+ }
+
+ fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
+ return false;
+}
+
+/**
+ * Get the string to indicate type of value expected for an argument.
+ *
+ * \param[in] type The argument type.
+ * \return String for value type.
+ */
+static const char *cli__string_from_type(enum cli_arg_type type)
+{
+ static const char *const strings[] = {
+ [CLI_BOOL] = "",
+ [CLI_INT] = "INT",
+ [CLI_UINT] = "UINT",
+ [CLI_ENUM] = "ENUM",
+ [CLI_STRING] = "STRING",
+ };
+
+ if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
+ return "";
+ }
+
+ return strings[type];
+}
+
+/**
+ * Helper to update a maximum adjusted string length if new values is greater.
+ *
+ * \param[in] str String to check.
+ * \param[in] adjustment Amount to modify length of string by (bytes).
+ * \param[out] len Returns the maximum of existing and this length.
+ */
+static void cli__max_len(const char *str, size_t adjustment, size_t *len)
+{
+ size_t str_len = strlen(str) + adjustment;
+
+ if (str_len > *len) {
+ *len = str_len;
+ }
+}
+
+/**
+ * Count up various properties of the client CLI interface specification.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[out] count Returns number of non-positional arguments.
+ * \param[out] pcount Returns number of positional arguments.
+ * \param[out] max_len Returns max string length of non-positional arguments.
+ * \param[out] pmax_len Returns max string length of positional arguments.
+ * \param[out] phas_desc Returns number of positional args with descriptions.
+ */
+static void cli__count(const struct cli_table *cli,
+ size_t *count,
+ size_t *pcount,
+ size_t *max_len,
+ size_t *pmax_len,
+ size_t *phas_desc)
+{
+ if (count != NULL) *count = 0;
+ if (pcount != NULL) *pcount = 0;
+ if (max_len != NULL) *max_len = 0;
+ if (pmax_len != NULL) *pmax_len = 0;
+ if (phas_desc != NULL) *phas_desc = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (cli__entry_is_positional(entry)) {
+ if (pcount != NULL) {
+ (*pcount)++;
+ }
+ if (pmax_len != NULL) {
+ cli__max_len(entry->l, 0, pmax_len);
+ }
+ if (phas_desc != NULL) {
+ (*phas_desc)++;
+ }
+ } else {
+ if (count != NULL) {
+ (*count)++;
+ }
+ if (max_len != NULL) {
+ const char *type_str;
+ size_t type_len;
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+
+ cli__max_len(entry->l, type_len, max_len);
+ }
+ }
+ }
+}
+
+static inline bool cli__is_negative(const char *arg)
+{
+ int64_t i;
+ size_t pos = 0;
+
+ return cli__parse_value_int(arg, &i, &pos)
+ && pos == strlen(arg)
+ && i < 0;
+}
+
+/* Documented in cli.h */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
+{
+ size_t pos_count = 0;
+ enum {
+ ARG_PROG_NAME,
+ ARG_FIRST,
+ };
+
+ for (int i = ARG_FIRST; i < argc; i++) {
+ const char *arg = argv[i];
+ size_t pos_inc = 0;
+ bool ret;
+
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ ret = cli__parse_long(cli, argc, argv, &i);
+ } else {
+ ret = cli__parse_short(cli, argc, argv, &i);
+ if (ret != true) {
+ if (cli__is_negative(argv[i])) {
+ pos_inc = 1;
+ ret = cli__parse_positional(
+ cli, argv[i],
+ pos_count);
+ }
+ }
+ }
+ } else {
+ pos_inc = 1;
+ ret = cli__parse_positional(cli, argv[i], pos_count);
+ }
+
+ if (ret != true) {
+ return ret;
+ }
+
+ pos_count += pos_inc;
+ }
+
+ if (pos_count < cli->min_positional) {
+ fprintf(stderr, "Insufficient positional arguments found.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Get terminal width.
+ *
+ * \return terminal width in characters.
+ */
+static size_t cli__terminal_width(void)
+{
+ return 80;
+}
+
+/**
+ * Print an entry's description, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] entry The entry to print the description for.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_description(const struct cli_table_entry *entry,
+ size_t indent)
+{
+ size_t terminal_width = cli__terminal_width();
+ size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
+ size_t space = avail;
+ const char *desc = entry->d;
+
+ if (desc != NULL) {
+ while (*desc != '\0') {
+ size_t word_len = strcspn(desc, " \n\t");
+ if (word_len <= space || space == avail) {
+ fprintf(stderr, "%*.*s",
+ (int)word_len,
+ (int)word_len, desc);
+ desc += word_len;
+ if (word_len <= space) {
+ space -= word_len;
+ }
+ if (space > 0) {
+ fprintf(stderr, " ");
+ space--;
+ }
+ } else {
+ fprintf(stderr, "\n%*s", (int)indent, "");
+ space = avail;
+ }
+ desc += strspn(desc, " \n\t");
+ }
+ }
+
+ fprintf(stderr, "\n");
+}
+
+/* Documented in cli.h */
+void cli_help(const struct cli_table *cli, const char *prog_name)
+{
+ size_t count;
+ size_t pcount;
+ size_t max_len;
+ size_t pmax_len;
+ size_t phas_desc;
+ size_t required = 0;
+ enum {
+ ARG_PROG_NAME,
+ };
+
+ cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
+
+ fprintf(stderr, "\nUsage: %s", prog_name);
+
+ if (pcount > 0) {
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ const char *punctuation =
+ (required == cli->min_positional) ?
+ " [" : " ";
+
+ if (cli->entries[i].t == CLI_CMD) {
+ fprintf(stderr, "%s%s", punctuation,
+ cli->entries[i].l);
+ } else {
+ fprintf(stderr, "%s<%s>", punctuation,
+ cli->entries[i].l);
+ }
+ required++;
+ }
+ }
+ if (required == pcount && required > cli->min_positional) {
+ fprintf(stderr, "]");
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, " [options]");
+ }
+
+ fprintf(stderr, "\n\n");
+
+ if (phas_desc > 0) {
+ fprintf(stderr, "Where:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (entry->d == NULL) {
+ continue;
+ }
+
+ if (cli__entry_is_positional(entry)) {
+ fprintf(stderr, " %*.*s ",
+ (int)pmax_len,
+ (int)pmax_len,
+ entry->l);
+ cli__print_description(entry, pmax_len + 4);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, "Options:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+ const char *type_str;
+ size_t type_len;
+ size_t arg_len;
+
+ if (cli__entry_is_positional(entry)) {
+ continue;
+ }
+
+ if (entry->s != '\0') {
+ fprintf(stderr, " -%c", entry->s);
+ } else {
+ fprintf(stderr, " ");
+ }
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+ arg_len = strlen(entry->l);
+
+ fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
+ (int)(max_len - arg_len - type_len),
+ "");
+ cli__print_description(entry, max_len + 11);
+ fprintf(stderr, "\n");
+ }
+ }
+}
diff --git a/test/cli.h b/test/cli.h
new file mode 100644
index 0000000..91db086
--- /dev/null
+++ b/test/cli.h
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling API.
+ */
+
+#ifndef _PELTAR_CLI_H_
+#define _PELTAR_CLI_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Helper to get element count for an array,
+ *
+ * \param[in] _a Array to get number of elements for.
+ */
+#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
+
+/**
+ * CLI argument type.
+ */
+enum cli_arg_type {
+ CLI_CMD, /**< A sub-command. Must match long argument name. */
+ CLI_BOOL, /**< Has no value; presence of flag indicates true. */
+ CLI_INT, /**< Has signed integer value. */
+ CLI_UINT, /**< Has unsigned integer value. */
+ CLI_ENUM, /**< Has enumeration value. */
+ CLI_STRING, /**< Has string value. */
+};
+
+struct cli_str_val {
+ const char *str;
+ int64_t val;
+};
+
+struct cli_enum {
+ const struct cli_str_val *desc;
+ int64_t *e; /**< Location to store \ref CLI_ENUM value. */
+};
+
+/**
+ * Client description for a command line argument.
+ */
+struct cli_table_entry {
+ const char *l; /**< Long argument name. */
+ const char s; /**< Short flag name. (Non-positional arguments.) */
+ bool p; /**< Whether the argument is a positional argument. */
+ enum cli_arg_type t; /**< Argument type. */
+ union {
+ bool *b; /**< Location to store \ref CLI_BOOL value. */
+ int64_t *i; /**< Location to store \ref CLI_INT value. */
+ uint64_t *u; /**< Location to store \ref CLI_UINT value. */
+ const char **s; /**< Location to store \ref CLI_STRING value. */
+ struct cli_enum e;
+ } v; /**< Where to store type-specific values. */
+ const char *d; /**< Description. */
+};
+
+/**
+ * Client command line interface specification.
+ */
+struct cli_table {
+ const struct cli_table_entry *entries;
+ size_t count;
+ size_t min_positional;
+};
+
+/**
+ * Parse the command line arguments.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \return true on success, false on error.
+ */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
+
+/**
+ * Print usage and help output.
+ *
+ * Note: Assumes non-Unicode. (One byte per character.)
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] prog_name Program name.
+ */
+void cli_help(const struct cli_table *cli, const char *prog_name);
+
+#endif
diff --git a/test/nsgif.c b/test/nsgif.c
index 173f70c..23fd3f3 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -17,8 +17,56 @@
#include "../include/nsgif.h"
+#include "cli.h"
+#include "cli.c"
+
#define BYTES_PER_PIXEL 4
+static struct nsgif_options {
+ const char *file;
+ const char *ppm;
+ uint64_t loops;
+ bool info;
+} nsgif_options;
+
+static const struct cli_table_entry cli_entries[] = {
+ {
+ .s = 'm',
+ .l = "ppm",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.ppm,
+ .d = "Convert frames to PPM image at given path."
+ },
+ {
+ .s = 'i',
+ .l = "info",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.info,
+ .d = "Dump GIF info to stdout."
+ },
+ {
+ .s = 'l',
+ .l = "loops",
+ .t = CLI_UINT,
+ .v.u = &nsgif_options.loops,
+ .d = "Loop through decoding all frames N times. "
+ "The default is 1."
+ },
+ {
+ .p = true,
+ .l = "FILE",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.file,
+ .d = "Path to GIF file to load."
+ },
+};
+
+const struct cli_table cli = {
+ .entries = cli_entries,
+ .count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
+ .min_positional = 1,
+};
+
static void *bitmap_create(int width, int height)
{
/* Ensure a stupidly large bitmap is not created */
@@ -84,7 +132,31 @@ static void warning(const char *context, nsgif_error err)
context, nsgif_strerror(err));
}
-static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
+static void print_gif_info(const nsgif_info_t *info)
+{
+ fprintf(stdout, "gif:\n");
+ fprintf(stdout, " width: %"PRIu32"\n", info->width);
+ fprintf(stdout, " height: %"PRIu32"\n", info->height);
+ fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
+ fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
+ fprintf(stdout, " frames:\n");
+}
+
+static void print_gif_frame_info(const nsgif_frame_info_t *info)
+{
+ const char *disposal = nsgif_str_disposal(info->disposal);
+
+ fprintf(stdout, " - disposal-method: %s\n", disposal);
+ fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
+ fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
+ fprintf(stdout, " rect:\n");
+ fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
+ fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
+ fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
+ fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
+}
+
+static void decode(FILE* ppm, const char *name, nsgif *gif)
{
nsgif_error err;
uint32_t frame_prev = 0;
@@ -92,20 +164,24 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
info = nsgif_get_info(gif);
- if (write_ppm) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", info->width);
- fprintf(fh, "# height %u \n", info->height);
- fprintf(fh, "# frame_count %u \n", info->frame_count);
- fprintf(fh, "# loop_max %u \n", info->loop_max);
- fprintf(fh, "%u %u 256\n", info->width,
+ if (ppm != NULL) {
+ fprintf(ppm, "P3\n");
+ fprintf(ppm, "# %s\n", name);
+ fprintf(ppm, "# width %u \n", info->width);
+ fprintf(ppm, "# height %u \n", info->height);
+ fprintf(ppm, "# frame_count %u \n", info->frame_count);
+ fprintf(ppm, "# loop_max %u \n", info->loop_max);
+ fprintf(ppm, "%u %u 256\n", info->width,
info->height * info->frame_count);
}
+ if (nsgif_options.info == true) {
+ print_gif_info(info);
+ }
+
/* decode the frames */
while (true) {
- nsgif_bitmap_t *buffer;
+ nsgif_bitmap_t *bitmap;
const uint8_t *image;
uint32_t frame_new;
uint32_t delay_cs;
@@ -120,31 +196,44 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
if (frame_new < frame_prev) {
/* Must be an animation that loops. We only care about
- * decoding each frame once. */
+ * decoding each frame once in this utility. */
return;
}
frame_prev = frame_new;
- err = nsgif_frame_decode(gif, frame_new, &buffer);
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
if (err != NSGIF_OK) {
warning("nsgif_decode_frame", err);
return;
}
- if (write_ppm) {
- fprintf(fh, "# frame %u:\n", frame_new);
- image = (const uint8_t *) buffer;
+ if (nsgif_options.info == true) {
+ const nsgif_frame_info_t *f_info;
+
+ f_info = nsgif_get_frame_info(gif, frame_new);
+ assert(f_info != NULL);
+ print_gif_frame_info(f_info);
+ }
+
+ if (ppm != NULL) {
+ fprintf(ppm, "# frame %u:\n", frame_new);
+ image = (const uint8_t *) bitmap;
for (uint32_t y = 0; y != info->height; y++) {
for (uint32_t x = 0; x != info->width; x++) {
size_t z = (y * info->width + x) * 4;
- fprintf(fh, "%u %u %u ",
+ fprintf(ppm, "%u %u %u ",
image[z],
image[z + 1],
image[z + 2]);
}
- fprintf(fh, "\n");
+ fprintf(ppm, "\n");
}
}
+
+ if (delay_cs == NSGIF_INFINITE) {
+ /** This frame is the last. */
+ return;
+ }
}
}
@@ -159,40 +248,32 @@ int main(int argc, char *argv[])
size_t size;
uint8_t *data;
nsgif_error err;
- FILE *outf = stdout;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
+ FILE *ppm = NULL;
+
+ /* Override default options with any command line args */
+ if (!cli_parse(&cli, argc, (void *)argv)) {
+ cli_help(&cli, argv[0]);
+ return EXIT_FAILURE;
}
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
+ if (nsgif_options.ppm != NULL) {
+ ppm = fopen(nsgif_options.ppm, "w+");
+ if (ppm == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ nsgif_options.ppm);
+ return EXIT_FAILURE;
}
}
/* create our gif animation */
err = nsgif_create(&bitmap_callbacks, &gif);
if (err != NSGIF_OK) {
- return 1;
+ warning("nsgif_create", err);
+ return EXIT_FAILURE;
}
/* load file into memory */
- data = load_file(argv[1], &size);
+ data = load_file(nsgif_options.file, &size);
/* Scan the raw data */
err = nsgif_data_scan(gif, size, data);
@@ -200,13 +281,15 @@ int main(int argc, char *argv[])
warning("nsgif_data_scan", err);
nsgif_destroy(gif);
free(data);
- return 1;
+ return EXIT_FAILURE;
}
- decode(outf, argv[1], gif, !no_write);
+ for (uint64_t i = 0; i < nsgif_options.loops; i++) {
+ decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
+ }
- if (argc > 2 && !no_write) {
- fclose(outf);
+ if (ppm != NULL) {
+ fclose(ppm);
}
/* clean up */
diff --git a/test/runtest.sh b/test/runtest.sh
index 47cec7e..fd84847 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -23,7 +23,7 @@ gifdecode()
OUTF=$(basename ${1} .gif)
CMPF=$(dirname ${1})/${OUTF}.ppm
echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_nsgif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} --ppm ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
ECODE=$?
echo "Exit code:${ECODE}" >> ${TEST_LOG}
-----------------------------------------------------------------------
Summary of changes:
README | 36 -----------------
README.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/nsgif.h | 2 +-
src/gif.c | 2 +-
test/nsgif.c | 10 +++--
5 files changed, 124 insertions(+), 42 deletions(-)
delete mode 100644 README
create mode 100644 README.md
diff --git a/README b/README
deleted file mode 100644
index 498ee46..0000000
--- a/README
+++ /dev/null
@@ -1,36 +0,0 @@
-libnsgif - Decoding GIF files
-=============================
-
-The functions provided by this library allow for efficient progressive
-GIF decoding. Whilst the initialisation does not ensure that there is
-sufficient image data to complete the entire frame, it does ensure
-that the information provided is valid. Any subsequent attempts to
-decode an initialised GIF are guaranteed to succeed, and any bytes of
-the image not present are assumed to be totally transparent.
-
-To begin decoding a GIF, the 'gif' structure must be initialised with
-the 'gif_data' and 'buffer_size' set to their initial values. The
-'buffer_position' should initially be 0, and will be internally
-updated as the decoding commences. The caller should then repeatedly
-call gif_initialise() with the structure until the function returns 1,
-or no more data is avaliable.
-
-Once the initialisation has begun, the decoder completes the variables
-'frame_count' and 'frame_count_partial'. The former being the total
-number of frames that have been successfully initialised, and the
-latter being the number of frames that a partial amount of data is
-available for. This assists the caller in managing the animation
-whilst decoding is continuing.
-
-To decode a frame, the caller must use gif_decode_frame() which
-updates the current 'frame_image' to reflect the desired frame. The
-required 'disposal_method' is also updated to reflect how the frame
-should be plotted. The caller must not assume that the current
-'frame_image' will be valid between calls if initialisation is still
-occuring, and should either always request that the frame is decoded
-(no processing will occur if the 'decoded_frame' has not been
-invalidated by initialisation) or perform the check itself.
-
-It should be noted that gif_finalise() should always be called, even
-if no frames were initialised. Additionally, it is the responsibility
-of the caller to free 'gif_data'.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d821ac6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,116 @@
+LibNSGIF: NetSurf GIF decoder
+=============================
+
+LibNSGIF is a C library for decoding GIF format images and animations.
+It is licenced under the MIT licence.
+
+This library aims to provide a simple API for robust decoding of GIF files.
+
+Details
+-------
+
+The GIF source data is scanned prior to decoding, allowing for efficient
+decoding. The scanning phase will scan currently available data and will
+resume from where it left off when called with additional data.
+
+Only one frame is ever fully decoded to a bitmap at a time, reducing memory
+usage for large GIFs.
+
+Using
+-----
+
+LibNSGIF allows the client to allocate the bitmap into which the GIF is
+decoded. The client can have an arbitrary bitmap structure, that is simply
+a void pointer to LibNSGIF. The client must provide a callback table for
+interacting with bitmaps. This table must include as a minimum functions to
+create and destroy bitmaps, and a function to get a pointer to the bitmap's
+pixel data buffer.
+
+To load a GIF, first create an nsgif object with `nsgif_create()`.
+
+```c
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Now you can load the GIF source data into the nsgif object with
+`nsgif_data_scan()`:
+
+```c
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+This scans the source data and decodes information about each frame, however
+it doesn't decode any of the bitmap data for the frames. The client may call
+`nsgif_data_scan()` multiple times as source data is fetched. Once the
+function has returned `NSGIF_OK` it has enough data to display at least one
+frame. The early frames can be decoded before the later frames are scanned.
+Frames have to be scanned before they can be decoded.
+
+To decode the frames, you can call `nsgif_get_info()` to get the frame_count,
+and then call `nsgif_frame_decode()` for each frame, and manage the animation,
+and non-displayable frames yourself, or you can use the helper function,
+`nsgif_frame_prepare()`:
+
+```c
+ err = nsgif_frame_prepare(gif, &area, &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+
+ // Update our bitmap to know it should be showing `frame_new` now.
+ // Trigger redraw of `area` of image.
+
+ if (delay_cs != NSGIF_INFINITE) {
+ // Schedule next frame in delay_cs.
+ }
+```
+
+This will return the number of the next frame to be decoded, the delay in cs
+before the next frame should be decoded, and the area of the bitmap that needs
+to be redrawn.
+
+> **Note**: GIF frames may only occupy a portion of the overall bitmap, and only
+> redrawing the area that has changed may be more efficient than redrawing the
+> whole thing. The returned area comprises both any region that has been
+> changed in the disposal of the previous frame and the new frame.
+
+GIF files can limit the number of animation loops to a finite number or they
+may only have one frame. In either of these cases, the returned delay is
+`NSGIF_INFINITE` indicating that the animation is complete. Subsequent calls
+to `nsgif_frame_prepare()` will return `NSGIF_ERR_ANIMATION_END`.
+
+To force the repeat of an animation, call `nsgif_reset()`.
+
+One reason for the two-step decoding of frames is that it enables deferred
+decoding. You can call `nsgif_frame_prepare()` and cause a redraw of that
+portion of your document. If the GIF is off screen (another tab, or scrolled
+out of sight), there is no need to decode it at all.
+
+Once the bitmap is needed for a redraw, you can decode the correct frame
+on-demand with:
+
+```c
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
+ if (err != NSGIF_OK) {
+ fprintf(stderr, "%s\n", nsgif_strerror(err));
+ // Handle error
+ }
+```
+
+Note that this will be a no-op if the requested frame already happens to be
+the decoded frame.
+
+Once you are done with the GIF, free up the nsgif object with:
+
+```c
+ nsgif_destroy(gif);
+```
diff --git a/include/nsgif.h b/include/nsgif.h
index 2c8a60c..b60747f 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -1,7 +1,7 @@
/*
* Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
diff --git a/src/gif.c b/src/gif.c
index 5317a9e..467ff90 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1,7 +1,7 @@
/*
* Copyright 2004 Richard Wilson <richard.wilson(a)netsurf-browser.org>
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2013-2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
diff --git a/test/nsgif.c b/test/nsgif.c
index 06defb9..b1b2424 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -1,6 +1,7 @@
/*
* Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
* Copyright 2008 James Bursa <james(a)netsurf-browser.org>
+ * Copyright 2022 Michael Drake <tlsa(a)netsurf-browser.org>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
@@ -181,7 +182,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
/* decode the frames */
while (true) {
- nsgif_bitmap_t *buffer;
+ nsgif_bitmap_t *bitmap;
const uint8_t *image;
uint32_t frame_new;
uint32_t delay_cs;
@@ -196,12 +197,12 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
if (frame_new < frame_prev) {
/* Must be an animation that loops. We only care about
- * decoding each frame once. */
+ * decoding each frame once in this utility. */
return;
}
frame_prev = frame_new;
- err = nsgif_frame_decode(gif, frame_new, &buffer);
+ err = nsgif_frame_decode(gif, frame_new, &bitmap);
if (err != NSGIF_OK) {
warning("nsgif_decode_frame", err);
return;
@@ -217,7 +218,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
if (ppm != NULL) {
fprintf(ppm, "# frame %u:\n", frame_new);
- image = (const uint8_t *) buffer;
+ image = (const uint8_t *) bitmap;
for (uint32_t y = 0; y != info->height; y++) {
for (uint32_t x = 0; x != info->width; x++) {
size_t z = (y * info->width + x) * 4;
@@ -231,6 +232,7 @@ static void decode(FILE* ppm, const char *name, nsgif_t *gif)
}
if (delay_cs == NSGIF_INFINITE) {
+ /** This frame is the last. */
return;
}
}
--
NetSurf GIF Decoder
1 year, 7 months
netsurf: branch tlsa/gif-api updated. release/3.10-159-g2e9ef8f
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/netsurf.git/shortlog/2e9ef8feae321d9a8463c...
...commit http://git.netsurf-browser.org/netsurf.git/commit/2e9ef8feae321d9a8463c81...
...tree http://git.netsurf-browser.org/netsurf.git/tree/2e9ef8feae321d9a8463c816a...
The branch, tlsa/gif-api has been updated
discards e1420000609e3b1c059ccfd09af5b02be2d59067 (commit)
via 2e9ef8feae321d9a8463c816a356eb73d789b6c7 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (e1420000609e3b1c059ccfd09af5b02be2d59067)
\
N -- N -- N (2e9ef8feae321d9a8463c816a356eb73d789b6c7)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
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/netsurf.git/commit/?id=2e9ef8feae321d9a846...
commit 2e9ef8feae321d9a8463c816a356eb73d789b6c7
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Update to latest libnsgif API.
diff --git a/content/handlers/image/gif.c b/content/handlers/image/gif.c
index e2a0ca5..0d4506a 100644
--- a/content/handlers/image/gif.c
+++ b/content/handlers/image/gif.c
@@ -34,7 +34,8 @@
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
-#include <libnsgif.h>
+
+#include <nsgif.h>
#include "utils/utils.h"
#include "utils/messages.h"
@@ -51,13 +52,31 @@
#include "image/image.h"
#include "image/gif.h"
-typedef struct nsgif_content {
+typedef struct gif_content {
struct content base;
- struct gif_animation *gif; /**< GIF animation data */
- int current_frame; /**< current frame to display [0...(max-1)] */
-} nsgif_content;
+ nsgif_t *gif; /**< GIF animation data */
+ uint32_t current_frame; /**< current frame to display [0...(max-1)] */
+} gif_content;
+
+static inline nserror gif__nsgif_error_to_ns(nsgif_error gif_res)
+{
+ nserror err;
+
+ switch (gif_res) {
+ case NSGIF_OK:
+ err = NSERROR_OK;
+ break;
+ case NSGIF_ERR_OOM:
+ err = NSERROR_NOMEM;
+ break;
+ default:
+ err = NSERROR_GIF_ERROR;
+ break;
+ }
+ return err;
+}
/**
* Callback for libnsgif; forwards the call to bitmap_create()
@@ -66,44 +85,42 @@ typedef struct nsgif_content {
* \param height width of image in pixels
* \return an opaque struct bitmap, or NULL on memory exhaustion
*/
-static void *nsgif_bitmap_create(int width, int height)
+static void *gif_bitmap_create(int width, int height)
{
return guit->bitmap->create(width, height, BITMAP_NEW);
}
-
-static nserror nsgif_create_gif_data(nsgif_content *c)
+static nserror gif_create_gif_data(gif_content *c)
{
- gif_bitmap_callback_vt gif_bitmap_callbacks = {
- .bitmap_create = nsgif_bitmap_create,
- .bitmap_destroy = guit->bitmap->destroy,
- .bitmap_get_buffer = guit->bitmap->get_buffer,
- .bitmap_set_opaque = guit->bitmap->set_opaque,
- .bitmap_test_opaque = guit->bitmap->test_opaque,
- .bitmap_modified = guit->bitmap->modified
+ nsgif_error gif_res;
+ const nsgif_bitmap_cb_vt gif_bitmap_callbacks = {
+ .create = gif_bitmap_create,
+ .destroy = guit->bitmap->destroy,
+ .get_buffer = guit->bitmap->get_buffer,
+ .set_opaque = guit->bitmap->set_opaque,
+ .test_opaque = guit->bitmap->test_opaque,
+ .modified = guit->bitmap->modified
};
- /* Initialise our data structure */
- c->gif = calloc(sizeof(gif_animation), 1);
- if (c->gif == NULL) {
- content_broadcast_error(&c->base, NSERROR_NOMEM, NULL);
- return NSERROR_NOMEM;
+ gif_res = nsgif_create(&gif_bitmap_callbacks, &c->gif);
+ if (gif_res != NSGIF_OK) {
+ nserror err = gif__nsgif_error_to_ns(gif_res);
+ content_broadcast_error(&c->base, err, NULL);
+ return err;
}
- gif_create(c->gif, &gif_bitmap_callbacks);
+
return NSERROR_OK;
}
-
-
-static nserror nsgif_create(const content_handler *handler,
- lwc_string *imime_type, const struct http_parameter *params,
+static nserror gif_create(const content_handler *handler,
+ lwc_string *imime_type, const struct http_parameter *params,
llcache_handle *llcache, const char *fallback_charset,
bool quirks, struct content **c)
{
- nsgif_content *result;
+ gif_content *result;
nserror error;
- result = calloc(1, sizeof(nsgif_content));
+ result = calloc(1, sizeof(gif_content));
if (result == NULL)
return NSERROR_NOMEM;
@@ -114,7 +131,7 @@ static nserror nsgif_create(const content_handler *handler,
return error;
}
- error = nsgif_create_gif_data(result);
+ error = gif_create_gif_data(result);
if (error != NSERROR_OK) {
free(result);
return error;
@@ -126,99 +143,65 @@ static nserror nsgif_create(const content_handler *handler,
}
/**
+ * Scheduler callback. Performs any necessary animation.
+ *
+ * \param p The content to animate
+*/
+static void gif_animate_cb(void *p);
+
+/**
* Performs any necessary animation.
*
* \param p The content to animate
*/
-static void nsgif_animate(void *p)
+static nserror gif__animate(gif_content *gif, bool redraw)
{
- nsgif_content *gif = p;
- union content_msg_data data;
- int delay;
- int f;
-
- /* Advance by a frame, updating the loop count accordingly */
- gif->current_frame++;
- if (gif->current_frame == (int)gif->gif->frame_count_partial) {
- gif->current_frame = 0;
-
- /* A loop count of 0 has a special meaning of infinite */
- if (gif->gif->loop_count != 0) {
- gif->gif->loop_count--;
- if (gif->gif->loop_count == 0) {
- gif->current_frame =
- gif->gif->frame_count_partial - 1;
- gif->gif->loop_count = -1;
- }
- }
+ nsgif_error gif_res;
+ nsgif_rect_t rect;
+ uint32_t delay;
+ uint32_t f;
+
+ gif_res = nsgif_frame_prepare(gif->gif, &rect, &delay, &f);
+ if (gif_res != NSGIF_OK) {
+ return gif__nsgif_error_to_ns(gif_res);
}
+ gif->current_frame = f;
+
/* Continue animating if we should */
- if (gif->gif->loop_count >= 0) {
- delay = gif->gif->frames[gif->current_frame].frame_delay;
- if (delay <= 1) {
- /* Assuming too fast to be intended, set default. */
- delay = 10;
- }
- guit->misc->schedule(delay * 10, nsgif_animate, gif);
+ if (nsoption_bool(animate_images) && delay != NSGIF_INFINITE) {
+ guit->misc->schedule(delay * 10, gif_animate_cb, gif);
}
- if ((!nsoption_bool(animate_images)) ||
- (!gif->gif->frames[gif->current_frame].display)) {
- return;
- }
+ if (redraw) {
+ union content_msg_data data;
- /* area within gif to redraw */
- f = gif->current_frame;
- data.redraw.x = gif->gif->frames[f].redraw_x;
- data.redraw.y = gif->gif->frames[f].redraw_y;
- data.redraw.width = gif->gif->frames[f].redraw_width;
- data.redraw.height = gif->gif->frames[f].redraw_height;
-
- /* redraw background (true) or plot on top (false) */
- if (gif->current_frame > 0) {
- /* previous frame needed clearing: expand the redraw area to
- * cover it */
- if (gif->gif->frames[f - 1].redraw_required) {
- if (data.redraw.x >
- (int)(gif->gif->frames[f - 1].redraw_x)) {
- data.redraw.width += data.redraw.x -
- gif->gif->frames[f - 1].redraw_x;
- data.redraw.x =
- gif->gif->frames[f - 1].redraw_x;
- }
- if (data.redraw.y >
- (int)(gif->gif->frames[f - 1].redraw_y)) {
- data.redraw.height += (data.redraw.y -
- gif->gif->frames[f - 1].redraw_y);
- data.redraw.y =
- gif->gif->frames[f - 1].redraw_y;
- }
- if ((int)(gif->gif->frames[f - 1].redraw_x +
- gif->gif->frames[f - 1].redraw_width) >
- (data.redraw.x + data.redraw.width))
- data.redraw.width =
- gif->gif->frames[f - 1].redraw_x -
- data.redraw.x +
- gif->gif->frames[f - 1].redraw_width;
- if ((int)(gif->gif->frames[f - 1].redraw_y +
- gif->gif->frames[f - 1].redraw_height) >
- (data.redraw.y + data.redraw.height))
- data.redraw.height =
- gif->gif->frames[f - 1].redraw_y -
- data.redraw.y +
- gif->gif->frames[f - 1].redraw_height;
- }
+ /* area within gif to redraw */
+ data.redraw.x = rect.x0;
+ data.redraw.y = rect.y0;
+ data.redraw.width = rect.x1 - rect.x0;
+ data.redraw.height = rect.y1 - rect.y0;
+
+ content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data);
}
- content_broadcast(&gif->base, CONTENT_MSG_REDRAW, &data);
+ return NSERROR_OK;
+}
+
+static void gif_animate_cb(void *p)
+{
+ gif_content *gif = p;
+
+ gif__animate(gif, true);
}
-static bool nsgif_convert(struct content *c)
+static bool gif_convert(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
- int res;
+ gif_content *gif = (gif_content *) c;
+ const nsgif_info_t *gif_info;
const uint8_t *data;
+ nsgif_error gif_err;
+ nserror err;
size_t size;
char *title;
@@ -226,37 +209,27 @@ static bool nsgif_convert(struct content *c)
data = content__get_source_data(c, &size);
/* Initialise the GIF */
- do {
- res = gif_initialise(gif->gif, size, (unsigned char *) data);
- if (res != GIF_OK && res != GIF_WORKING &&
- res != GIF_INSUFFICIENT_FRAME_DATA) {
- nserror error = NSERROR_UNKNOWN;
- switch (res) {
- case GIF_FRAME_DATA_ERROR:
- case GIF_INSUFFICIENT_DATA:
- case GIF_DATA_ERROR:
- error = NSERROR_GIF_ERROR;
- break;
- case GIF_INSUFFICIENT_MEMORY:
- error = NSERROR_NOMEM;
- break;
- }
- content_broadcast_error(c, error, NULL);
- return false;
- }
- } while (res != GIF_OK && res != GIF_INSUFFICIENT_FRAME_DATA);
+ gif_err = nsgif_data_scan(gif->gif, size, data);
+ if (gif_err != NSGIF_OK) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, nsgif_strerror(gif_err));
+ return false;
+ }
+
+ gif_info = nsgif_get_info(gif->gif);
+ assert(gif_info != NULL);
/* Abort on bad GIFs */
- if ((gif->gif->frame_count_partial == 0) || (gif->gif->width == 0) ||
- (gif->gif->height == 0)) {
- content_broadcast_error(c, NSERROR_GIF_ERROR, NULL);
+ if (gif_info->height == 0) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, "Zero height image.");
return false;
}
/* Store our content width, height and calculate size */
- c->width = gif->gif->width;
- c->height = gif->gif->height;
- c->size += (gif->gif->width * gif->gif->height * 4) + 16 + 44;
+ c->width = gif_info->width;
+ c->height = gif_info->height;
+ c->size += (gif_info->width * gif_info->height * 4) + 16 + 44;
/* set title text */
title = messages_get_buff("GIFTitle",
@@ -267,12 +240,11 @@ static bool nsgif_convert(struct content *c)
free(title);
}
- /* Schedule the animation if we have one */
- gif->current_frame = 0;
- if (gif->gif->frame_count_partial > 1)
- guit->misc->schedule(gif->gif->frames[0].frame_delay * 10,
- nsgif_animate,
- c);
+ err = gif__animate(gif, false);
+ if (err != NSERROR_OK) {
+ content_broadcast_error(c, NSERROR_GIF_ERROR, NULL);
+ return false;
+ }
/* Exit as a success */
content_set_ready(c);
@@ -283,68 +255,51 @@ static bool nsgif_convert(struct content *c)
return true;
}
-
/**
* Updates the GIF bitmap to display the current frame
*
* \param gif The gif context to update.
- * \return GIF_OK on success else apropriate error code.
+ * \return NSGIF_OK on success else apropriate error code.
*/
-static gif_result nsgif_get_frame(nsgif_content *gif)
+static nsgif_error gif_get_frame(gif_content *gif,
+ nsgif_bitmap_t **bitmap)
{
- int previous_frame, current_frame, frame;
- gif_result res = GIF_OK;
-
- current_frame = gif->current_frame;
+ uint32_t current_frame = gif->current_frame;
if (!nsoption_bool(animate_images)) {
current_frame = 0;
}
- if (current_frame < gif->gif->decoded_frame) {
- previous_frame = 0;
- } else {
- previous_frame = gif->gif->decoded_frame + 1;
- }
-
- for (frame = previous_frame; frame <= current_frame; frame++) {
- res = gif_decode_frame(gif->gif, frame);
- }
-
- return res;
+ return nsgif_frame_decode(gif->gif, current_frame, bitmap);
}
-static bool nsgif_redraw(struct content *c, struct content_redraw_data *data,
+static bool gif_redraw(struct content *c, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK) {
- return false;
- }
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return false;
}
- return image_bitmap_plot(gif->gif->frame_image, data, clip, ctx);
+ return image_bitmap_plot(bitmap, data, clip, ctx);
}
-
-static void nsgif_destroy(struct content *c)
+static void gif_destroy(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
/* Free all the associated memory buffers */
- guit->misc->schedule(-1, nsgif_animate, c);
- gif_finalise(gif->gif);
- free(gif->gif);
+ guit->misc->schedule(-1, gif_animate_cb, c);
+ nsgif_destroy(gif->gif);
}
-
-static nserror nsgif_clone(const struct content *old, struct content **newc)
+static nserror gif_clone(const struct content *old, struct content **newc)
{
- nsgif_content *gif;
+ gif_content *gif;
nserror error;
- gif = calloc(1, sizeof(nsgif_content));
+ gif = calloc(1, sizeof(gif_content));
if (gif == NULL)
return NSERROR_NOMEM;
@@ -355,7 +310,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
}
/* Simply replay creation and conversion of content */
- error = nsgif_create_gif_data(gif);
+ error = gif_create_gif_data(gif);
if (error != NSERROR_OK) {
content_destroy(&gif->base);
return error;
@@ -363,7 +318,7 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
if (old->status == CONTENT_STATUS_READY ||
old->status == CONTENT_STATUS_DONE) {
- if (nsgif_convert(&gif->base) == false) {
+ if (gif_convert(&gif->base) == false) {
content_destroy(&gif->base);
return NSERROR_CLONE_FAILED;
}
@@ -374,9 +329,9 @@ static nserror nsgif_clone(const struct content *old, struct content **newc)
return NSERROR_OK;
}
-static void nsgif_add_user(struct content *c)
+static void gif_add_user(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
/* Ensure this content has already been converted.
* If it hasn't, the animation will start at the conversion phase instead. */
@@ -384,67 +339,66 @@ static void nsgif_add_user(struct content *c)
if (content_count_users(c) == 1) {
/* First user, and content already converted, so start the animation. */
- if (gif->gif->frame_count_partial > 1) {
- guit->misc->schedule(gif->gif->frames[0].frame_delay * 10,
- nsgif_animate, c);
+ if (nsgif_reset(gif->gif) == NSGIF_OK) {
+ gif__animate(gif, true);
}
}
}
-static void nsgif_remove_user(struct content *c)
+static void gif_remove_user(struct content *c)
{
if (content_count_users(c) == 1) {
/* Last user is about to be removed from this content, so stop the animation. */
- guit->misc->schedule(-1, nsgif_animate, c);
+ guit->misc->schedule(-1, gif_animate_cb, c);
}
}
-static void *nsgif_get_internal(const struct content *c, void *context)
+static nsgif_bitmap_t *gif_get_bitmap(
+ const struct content *c, void *context)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK)
- return NULL;
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return NULL;
}
- return gif->gif->frame_image;
+ return bitmap;
}
-static content_type nsgif_content_type(void)
+static content_type gif_content_type(void)
{
return CONTENT_IMAGE;
}
-static bool nsgif_content_is_opaque(struct content *c)
+static bool gif_content_is_opaque(struct content *c)
{
- nsgif_content *gif = (nsgif_content *) c;
+ gif_content *gif = (gif_content *) c;
+ nsgif_bitmap_t *bitmap;
- if (gif->current_frame != gif->gif->decoded_frame) {
- if (nsgif_get_frame(gif) != GIF_OK) {
- return false;
- }
+ if (gif_get_frame(gif, &bitmap) != NSGIF_OK) {
+ return false;
}
- return guit->bitmap->get_opaque(gif->gif->frame_image);
+ return guit->bitmap->get_opaque(bitmap);
}
-static const content_handler nsgif_content_handler = {
- .create = nsgif_create,
- .data_complete = nsgif_convert,
- .destroy = nsgif_destroy,
- .redraw = nsgif_redraw,
- .clone = nsgif_clone,
- .add_user = nsgif_add_user,
- .remove_user = nsgif_remove_user,
- .get_internal = nsgif_get_internal,
- .type = nsgif_content_type,
- .is_opaque = nsgif_content_is_opaque,
+static const content_handler gif_content_handler = {
+ .create = gif_create,
+ .data_complete = gif_convert,
+ .destroy = gif_destroy,
+ .redraw = gif_redraw,
+ .clone = gif_clone,
+ .add_user = gif_add_user,
+ .remove_user = gif_remove_user,
+ .get_internal = gif_get_bitmap,
+ .type = gif_content_type,
+ .is_opaque = gif_content_is_opaque,
.no_share = false,
};
-static const char *nsgif_types[] = {
+static const char *gif_types[] = {
"image/gif"
};
-CONTENT_FACTORY_REGISTER_TYPES(nsgif, nsgif_types, nsgif_content_handler);
+CONTENT_FACTORY_REGISTER_TYPES(nsgif, gif_types, gif_content_handler);
-----------------------------------------------------------------------
Summary of changes:
content/handlers/image/gif.c | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/content/handlers/image/gif.c b/content/handlers/image/gif.c
index fc896f8..0d4506a 100644
--- a/content/handlers/image/gif.c
+++ b/content/handlers/image/gif.c
@@ -55,11 +55,11 @@
typedef struct gif_content {
struct content base;
- nsgif *gif; /**< GIF animation data */
+ nsgif_t *gif; /**< GIF animation data */
uint32_t current_frame; /**< current frame to display [0...(max-1)] */
} gif_content;
-static inline nserror gif__nsgif_error_to_ns(nsgif_result gif_res)
+static inline nserror gif__nsgif_error_to_ns(nsgif_error gif_res)
{
nserror err;
@@ -67,7 +67,7 @@ static inline nserror gif__nsgif_error_to_ns(nsgif_result gif_res)
case NSGIF_OK:
err = NSERROR_OK;
break;
- case NSGIF_INSUFFICIENT_MEMORY:
+ case NSGIF_ERR_OOM:
err = NSERROR_NOMEM;
break;
default:
@@ -92,7 +92,7 @@ static void *gif_bitmap_create(int width, int height)
static nserror gif_create_gif_data(gif_content *c)
{
- nsgif_result gif_res;
+ nsgif_error gif_res;
const nsgif_bitmap_cb_vt gif_bitmap_callbacks = {
.create = gif_bitmap_create,
.destroy = guit->bitmap->destroy,
@@ -156,8 +156,8 @@ static void gif_animate_cb(void *p);
*/
static nserror gif__animate(gif_content *gif, bool redraw)
{
- nsgif_result gif_res;
- nsgif_rect rect;
+ nsgif_error gif_res;
+ nsgif_rect_t rect;
uint32_t delay;
uint32_t f;
@@ -200,7 +200,7 @@ static bool gif_convert(struct content *c)
gif_content *gif = (gif_content *) c;
const nsgif_info_t *gif_info;
const uint8_t *data;
- nsgif_result res;
+ nsgif_error gif_err;
nserror err;
size_t size;
char *title;
@@ -209,10 +209,10 @@ static bool gif_convert(struct content *c)
data = content__get_source_data(c, &size);
/* Initialise the GIF */
- res = nsgif_data_scan(gif->gif, size, data);
- if (res != NSGIF_OK) {
- err = gif__nsgif_error_to_ns(res);
- content_broadcast_error(c, err, NULL);
+ gif_err = nsgif_data_scan(gif->gif, size, data);
+ if (gif_err != NSGIF_OK) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, nsgif_strerror(gif_err));
return false;
}
@@ -220,10 +220,9 @@ static bool gif_convert(struct content *c)
assert(gif_info != NULL);
/* Abort on bad GIFs */
- if (res != NSGIF_INSUFFICIENT_FRAME_DATA ||
- gif_info->height == 0) {
- err = gif__nsgif_error_to_ns(res);
- content_broadcast_error(c, err, NULL);
+ if (gif_info->height == 0) {
+ err = gif__nsgif_error_to_ns(gif_err);
+ content_broadcast_error(c, err, "Zero height image.");
return false;
}
@@ -262,7 +261,7 @@ static bool gif_convert(struct content *c)
* \param gif The gif context to update.
* \return NSGIF_OK on success else apropriate error code.
*/
-static nsgif_result gif_get_frame(gif_content *gif,
+static nsgif_error gif_get_frame(gif_content *gif,
nsgif_bitmap_t **bitmap)
{
uint32_t current_frame = gif->current_frame;
--
NetSurf Browser
1 year, 7 months
libnsgif: branch tlsa/gif-api updated. release/0.2.1-149-g4a037e5
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/4a037e58acc229ba133e...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/4a037e58acc229ba133e72...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/4a037e58acc229ba133e727b...
The branch, tlsa/gif-api has been updated
via 4a037e58acc229ba133e727b15b3fd3d1d3b0c5b (commit)
via 02b1c6de36df8f4cd0b1eea52dafbd8840756f95 (commit)
via c88d94dfa6b4bb17a2d9855feb686025d1eb5be7 (commit)
via fe85229b8eb2ec6a160e8dd8bf44a3d563b53c1e (commit)
via 370c2a783b67350143c18a07463835887d2b1847 (commit)
via 84a9edb126121cd145295e48a5f0a98de4aab708 (commit)
via ff86c40667cdaa2a535c992f908ba15912d93d59 (commit)
via a0b9fcb4fa4c53319726044c8278a551293af817 (commit)
via dde30d3d4134c46439fb1984eeb88ac2d843fd60 (commit)
via d7a746a7af4265825add1d6ecd44d0699c8d238d (commit)
via f19985bde06da13b2a1396756b2c82300296f8c8 (commit)
from aad22e646509b7393f89b77f59894fba52031b7c (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=4a037e58acc229ba13...
commit 4a037e58acc229ba133e727b15b3fd3d1d3b0c5b
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Simplify frame delay calculation.
diff --git a/src/gif.c b/src/gif.c
index db99afd..5317a9e 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1618,7 +1618,6 @@ nsgif_error nsgif_frame_prepare(
};
uint32_t delay = 0;
uint32_t frame = gif->frame;
- uint32_t frame_next;
if (gif->frame != NSGIF_FRAME_INVALID &&
gif->frame < gif->info.frame_count &&
@@ -1632,7 +1631,7 @@ nsgif_error nsgif_frame_prepare(
return NSGIF_ERR_ANIMATION_END;
}
- ret = nsgif__next_displayable_frame(gif, &frame, NULL);
+ ret = nsgif__next_displayable_frame(gif, &frame, &delay);
if (ret != NSGIF_OK) {
return ret;
}
@@ -1641,17 +1640,22 @@ nsgif_error nsgif_frame_prepare(
gif->info.loop_count++;
}
- frame_next = frame;
- ret = nsgif__next_displayable_frame(gif, &frame_next, &delay);
- if (ret != NSGIF_OK) {
- return ret;
- }
+ if (gif->info.frame_count == 1) {
+ delay = NSGIF_INFINITE;
+
+ } else if (gif->info.loop_max != 0) {
+ uint32_t frame_next = frame;
+ ret = nsgif__next_displayable_frame(gif, &frame_next, NULL);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
- if (frame_next < frame) {
- if (nsgif__animation_complete(
- gif->info.loop_count + 1,
- gif->info.loop_max)) {
- delay = NSGIF_INFINITE;
+ if (frame_next < frame) {
+ if (nsgif__animation_complete(
+ gif->info.loop_count + 1,
+ gif->info.loop_max)) {
+ delay = NSGIF_INFINITE;
+ }
}
}
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=02b1c6de36df8f4cd0...
commit 02b1c6de36df8f4cd0b1eea52dafbd8840756f95
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Don't error for a final partial frame.
diff --git a/src/gif.c b/src/gif.c
index 3eebbd6..db99afd 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1517,9 +1517,8 @@ nsgif_error nsgif_data_scan(
ret = nsgif__process_frame(gif, frames, false);
} while (gif->info.frame_count > frames);
- if (ret == NSGIF_ERR_END_OF_DATA &&
- gif->info.frame_count > 0) {
- ret = NSGIF_ERR_END_OF_FRAME;
+ if (ret == NSGIF_ERR_END_OF_DATA && gif->info.frame_count > 0) {
+ ret = NSGIF_OK;
}
return ret;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=c88d94dfa6b4bb17a2...
commit c88d94dfa6b4bb17a2d9855feb686025d1eb5be7
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
API: Consistent typedef naming convention.
diff --git a/include/nsgif.h b/include/nsgif.h
index b36af8a..2c8a60c 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -23,7 +23,7 @@
/** Representation of infinity. */
#define NSGIF_INFINITE (UINT32_MAX)
-typedef struct nsgif nsgif;
+typedef struct nsgif nsgif_t;
/**
* GIF rectangle structure.
@@ -42,7 +42,7 @@ typedef struct nsgif_rect {
uint32_t x1;
/** y co-ordinate of redraw rectangle, bottom */
uint32_t y1;
-} nsgif_rect;
+} nsgif_rect_t;
/**
* NSGIF return codes.
@@ -178,14 +178,16 @@ const char *nsgif_strerror(nsgif_error err);
*
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
+nsgif_error nsgif_create(
+ const nsgif_bitmap_cb_vt *bitmap_vt,
+ nsgif_t **gif_out);
/**
* Free a NSGIF object.
*
* \param[in] gif The NSGIF to free.
*/
-void nsgif_destroy(nsgif *gif);
+void nsgif_destroy(nsgif_t *gif);
/**
* Scan the source image data.
@@ -212,7 +214,7 @@ void nsgif_destroy(nsgif *gif);
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_data_scan(
- nsgif *gif,
+ nsgif_t *gif,
size_t size,
const uint8_t *data);
@@ -231,8 +233,8 @@ nsgif_error nsgif_data_scan(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new);
@@ -247,7 +249,7 @@ nsgif_error nsgif_frame_prepare(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_frame_decode(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap);
@@ -264,7 +266,7 @@ nsgif_error nsgif_frame_decode(
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
nsgif_error nsgif_reset(
- nsgif *gif);
+ nsgif_t *gif);
/**
* Information about a GIF.
@@ -317,7 +319,7 @@ typedef struct nsgif_frame_info {
uint32_t delay;
/** Frame's redraw rectangle. */
- nsgif_rect rect;
+ nsgif_rect_t rect;
} nsgif_frame_info_t;
/**
@@ -327,7 +329,7 @@ typedef struct nsgif_frame_info {
*
* \return The gif info, or NULL on error.
*/
-const nsgif_info_t *nsgif_get_info(const nsgif *gif);
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
/**
* Get information about a GIF from an NSGIF object.
@@ -338,7 +340,7 @@ const nsgif_info_t *nsgif_get_info(const nsgif *gif);
* \return The gif frame info, or NULL on error.
*/
const nsgif_frame_info_t *nsgif_get_frame_info(
- const nsgif *gif,
+ const nsgif_t *gif,
uint32_t frame);
#endif
diff --git a/src/gif.c b/src/gif.c
index a5f059e..3eebbd6 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1257,7 +1257,7 @@ cleanup:
}
/* exported function documented in nsgif.h */
-void nsgif_destroy(nsgif *gif)
+void nsgif_destroy(nsgif_t *gif)
{
if (gif == NULL) {
return;
@@ -1283,9 +1283,9 @@ void nsgif_destroy(nsgif *gif)
}
/* exported function documented in nsgif.h */
-nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
{
- nsgif *gif;
+ nsgif_t *gif;
gif = calloc(1, sizeof(*gif));
if (gif == NULL) {
@@ -1389,7 +1389,7 @@ static nsgif_error nsgif__parse_logical_screen_descriptor(
/* exported function documented in nsgif.h */
nsgif_error nsgif_data_scan(
- nsgif *gif,
+ nsgif_t *gif,
size_t size,
const uint8_t *data)
{
@@ -1525,7 +1525,9 @@ nsgif_error nsgif_data_scan(
return ret;
}
-static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redraw)
+static void nsgif__redraw_rect_extend(
+ const nsgif_rect_t *frame,
+ nsgif_rect_t *redraw)
{
if (redraw->x1 == 0 || redraw->y1 == 0) {
*redraw = *frame;
@@ -1546,7 +1548,7 @@ static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redra
}
static uint32_t nsgif__frame_next(
- nsgif *gif,
+ nsgif_t *gif,
bool partial,
uint32_t frame)
{
@@ -1563,7 +1565,7 @@ static uint32_t nsgif__frame_next(
}
static nsgif_error nsgif__next_displayable_frame(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t *frame,
uint32_t *delay)
{
@@ -1595,7 +1597,7 @@ static inline bool nsgif__animation_complete(int count, int max)
}
nsgif_error nsgif_reset(
- nsgif *gif)
+ nsgif_t *gif)
{
gif->info.loop_count = 0;
gif->frame = NSGIF_FRAME_INVALID;
@@ -1605,13 +1607,13 @@ nsgif_error nsgif_reset(
/* exported function documented in nsgif.h */
nsgif_error nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new)
{
nsgif_error ret;
- nsgif_rect rect = {
+ nsgif_rect_t rect = {
.x1 = 0,
.y1 = 0,
};
@@ -1666,7 +1668,7 @@ nsgif_error nsgif_frame_prepare(
/* exported function documented in nsgif.h */
nsgif_error nsgif_frame_decode(
- nsgif *gif,
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap)
{
@@ -1702,14 +1704,14 @@ nsgif_error nsgif_frame_decode(
}
/* exported function documented in nsgif.h */
-const nsgif_info_t *nsgif_get_info(const nsgif *gif)
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif)
{
return &gif->info;
}
/* exported function documented in nsgif.h */
const nsgif_frame_info_t *nsgif_get_frame_info(
- const nsgif *gif,
+ const nsgif_t *gif,
uint32_t frame)
{
if (frame > gif->info.frame_count) {
diff --git a/test/nsgif.c b/test/nsgif.c
index 94e7960..06defb9 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -156,7 +156,7 @@ static void print_gif_frame_info(const nsgif_frame_info_t *info)
fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
}
-static void decode(FILE* ppm, const char *name, nsgif *gif)
+static void decode(FILE* ppm, const char *name, nsgif_t *gif)
{
nsgif_error err;
uint32_t frame_prev = 0;
@@ -185,7 +185,7 @@ static void decode(FILE* ppm, const char *name, nsgif *gif)
const uint8_t *image;
uint32_t frame_new;
uint32_t delay_cs;
- nsgif_rect area;
+ nsgif_rect_t area;
err = nsgif_frame_prepare(gif, &area,
&delay_cs, &frame_new);
@@ -243,8 +243,8 @@ int main(int argc, char *argv[])
.destroy = bitmap_destroy,
.get_buffer = bitmap_get_buffer,
};
- nsgif *gif;
size_t size;
+ nsgif_t *gif;
uint8_t *data;
nsgif_error err;
FILE *ppm = NULL;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=fe85229b8eb2ec6a16...
commit fe85229b8eb2ec6a160e8dd8bf44a3d563b53c1e
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Test: Add support for gif info dump and loop count to nsgif tool.
diff --git a/test/cli.c b/test/cli.c
new file mode 100644
index 0000000..031cd97
--- /dev/null
+++ b/test/cli.c
@@ -0,0 +1,763 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cli.h"
+
+/**
+ * Check whether a CLI argument type should have a numerical value.
+ *
+ * \param[in] type An argument type.
+ * \return true if the argument needs a numerical value, or false otherwise.
+ */
+static inline bool cli__arg_is_numerical(enum cli_arg_type type)
+{
+ return (type != CLI_STRING && type != CLI_BOOL);
+}
+
+/**
+ * Parse a signed integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] i Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_int(
+ const char *str,
+ int64_t *i,
+ size_t *pos)
+{
+ long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoll(str, &end, 0);
+
+ if (end == str || errno == ERANGE ||
+ temp > INT64_MAX || temp < INT64_MIN) {
+ fprintf(stderr, "Failed to parse integer from '%s'\n", str);
+ return false;
+ }
+
+ *i = (int64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an unsigned integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] u Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_uint(
+ const char *str,
+ uint64_t *u,
+ size_t *pos)
+{
+ unsigned long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoull(str, &end, 0);
+
+ if (end == str || errno == ERANGE || temp > UINT64_MAX) {
+ fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
+ return false;
+ }
+
+ *u = (uint64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an enum value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] e Enum details.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_enum(
+ const char *str,
+ const struct cli_enum *e,
+ size_t *pos)
+{
+ str += *pos;
+ *pos += strlen(str);
+
+ for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
+ if (strcmp(str, sv->str) == 0) {
+ *e->e = sv->val;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Parse a string value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] s Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_string(
+ const char *str,
+ const char **s,
+ size_t *pos)
+{
+ *s = str + *pos;
+ *pos += strlen(*s);
+ return true;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse a value from.
+ * \param[in,out] pos Current position in argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value(
+ const struct cli_table_entry *entry,
+ const char *arg,
+ size_t *pos)
+{
+ switch (entry->t) {
+ case CLI_CMD:
+ if (strcmp(arg + *pos, entry->l) == 0) {
+ *pos += strlen(arg);
+ return true;
+ }
+ return false;
+
+ case CLI_INT:
+ return cli__parse_value_int(arg, entry->v.i, pos);
+
+ case CLI_UINT:
+ return cli__parse_value_uint(arg, entry->v.u, pos);
+
+ case CLI_ENUM:
+ return cli__parse_value_enum(arg, &entry->v.e, pos);
+
+ case CLI_STRING:
+ return cli__parse_value_string(arg, entry->v.s, pos);
+
+ default:
+ fprintf(stderr, "Unexpected value for '%s': %s\n",
+ entry->l, arg);
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in] arg_pos Current position in argv.
+ * \param[in,out] pos Current pos in current argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_argv_value(const struct cli_table_entry *entry,
+ int argc, const char **argv,
+ int arg_pos, size_t *pos)
+{
+ const char *arg = argv[arg_pos];
+
+ if (arg_pos >= argc) {
+ fprintf(stderr, "Value not given for '%s'\n", entry->l);
+ return false;
+ }
+
+ return cli__parse_value(entry, arg, pos);
+}
+
+/**
+ * Check whether a CLI argument is a positional value.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \return true if the argument is positional, or false otherwise.
+ */
+static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
+{
+ return entry->p;
+}
+
+/**
+ * Look up a short argument flag.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] s Argument flag to look up in client CLI spec.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_short(
+ const struct cli_table *cli, char s)
+{
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ continue;
+ }
+ if (cli->entries[i].s == s) {
+ return &cli->entries[i];
+ }
+ }
+
+ fprintf(stderr, "Unknown flag: '%c'\n", s);
+ return NULL;
+}
+
+/**
+ * Handle an argument with a type that requires a value.
+ *
+ * This can handle the value being in the current argument, optionally split by
+ * a separator, or in the next argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in,out] arg_pos Current position in argv, updated on exit.
+ * \param[in] pos Current position in current argument string.
+ * \param[in] sep Name/value separator character, or '\0' if none.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__handle_arg_value(const struct cli_table_entry *entry,
+ int argc, const char **argv, int *arg_pos, size_t pos, char sep)
+{
+ const char *arg = argv[*arg_pos];
+ size_t orig_pos;
+ bool ret;
+
+ if (arg[pos] == '\0') {
+ (*arg_pos)++;
+ pos = 0;
+ } else if (arg[pos] == sep) {
+ pos++;
+ } else if (cli__arg_is_numerical(entry->t) == false) {
+ fprintf(stderr, "Separator required for non-numerical value\n");
+ return false;
+ }
+
+ if (isspace(argv[*arg_pos][pos])) {
+ fprintf(stderr, "Unexpected white space in '%s' "
+ "for argument '%s'\n",
+ &argv[*arg_pos][pos], entry->l);
+ return false;
+ }
+
+ orig_pos = pos;
+ ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
+ if (ret != true) {
+ return ret;
+ }
+
+ if (argv[*arg_pos][pos] != '\0') {
+ fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
+ &argv[*arg_pos][orig_pos], entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a flags argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_short(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const char *arg = argv[*arg_pos];
+ size_t pos = 1;
+
+ if (arg[0] != '-') {
+ return false;
+ }
+
+ while (arg[pos] != '\0') {
+ const struct cli_table_entry *entry;
+
+ entry = cli__lookup_short(cli, arg[pos]);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ *entry->v.b = true;
+ } else {
+ return cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos + 1, '\0');
+ }
+
+ pos++;
+ }
+
+ return true;
+}
+
+/**
+ * Look up a long argument name.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument name to look up in cli spec.
+ * \param[in,out] pos Current position in arg, updated on exit.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_long(
+ const struct cli_table *cli,
+ const char *arg,
+ size_t *pos)
+{
+ arg += *pos;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i]) == false) {
+ const char *name = cli->entries[i].l;
+ size_t name_len = strlen(cli->entries[i].l);
+
+ if (strncmp(name, arg, name_len) == 0) {
+ if (arg[name_len] != '\0' &&
+ arg[name_len] != '=') {
+ continue;
+ }
+ *pos += name_len;
+ return &cli->entries[i];
+ }
+ }
+ }
+
+ fprintf(stderr, "Unknown argument: '%s'\n", arg);
+ return NULL;
+}
+
+/**
+ * Parse a long argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_long(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const struct cli_table_entry *entry;
+ const char *arg = argv[*arg_pos];
+ size_t pos = 2;
+
+ if (arg[0] != '-' ||
+ arg[1] != '-') {
+ return false;
+ }
+
+ entry = cli__lookup_long(cli, arg, &pos);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ if (arg[pos] != '\0') {
+ fprintf(stderr, "Unexpected value for argument '%s'\n",
+ arg);
+ return false;
+ }
+ *entry->v.b = true;
+ } else {
+ bool ret;
+
+ ret = cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos, '=');
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument according to the given CLI spec entry.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional_entry(
+ const struct cli_table_entry *entry,
+ const char *arg)
+{
+ size_t pos = 0;
+ bool ret;
+
+ ret = cli__parse_value(entry, arg, &pos);
+ if (ret != true) {
+ return ret;
+ } else if (arg[pos] != '\0') {
+ fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
+ arg, entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument to parse.
+ * \param[in] count Number of positional arguments parsed already.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional(const struct cli_table *cli,
+ const char *arg, size_t count)
+{
+ size_t positional = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ if (positional == count) {
+ return cli__parse_positional_entry(
+ &cli->entries[i], arg);
+ }
+
+ positional++;
+ }
+ }
+
+ fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
+ return false;
+}
+
+/**
+ * Get the string to indicate type of value expected for an argument.
+ *
+ * \param[in] type The argument type.
+ * \return String for value type.
+ */
+static const char *cli__string_from_type(enum cli_arg_type type)
+{
+ static const char *const strings[] = {
+ [CLI_BOOL] = "",
+ [CLI_INT] = "INT",
+ [CLI_UINT] = "UINT",
+ [CLI_ENUM] = "ENUM",
+ [CLI_STRING] = "STRING",
+ };
+
+ if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
+ return "";
+ }
+
+ return strings[type];
+}
+
+/**
+ * Helper to update a maximum adjusted string length if new values is greater.
+ *
+ * \param[in] str String to check.
+ * \param[in] adjustment Amount to modify length of string by (bytes).
+ * \param[out] len Returns the maximum of existing and this length.
+ */
+static void cli__max_len(const char *str, size_t adjustment, size_t *len)
+{
+ size_t str_len = strlen(str) + adjustment;
+
+ if (str_len > *len) {
+ *len = str_len;
+ }
+}
+
+/**
+ * Count up various properties of the client CLI interface specification.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[out] count Returns number of non-positional arguments.
+ * \param[out] pcount Returns number of positional arguments.
+ * \param[out] max_len Returns max string length of non-positional arguments.
+ * \param[out] pmax_len Returns max string length of positional arguments.
+ * \param[out] phas_desc Returns number of positional args with descriptions.
+ */
+static void cli__count(const struct cli_table *cli,
+ size_t *count,
+ size_t *pcount,
+ size_t *max_len,
+ size_t *pmax_len,
+ size_t *phas_desc)
+{
+ if (count != NULL) *count = 0;
+ if (pcount != NULL) *pcount = 0;
+ if (max_len != NULL) *max_len = 0;
+ if (pmax_len != NULL) *pmax_len = 0;
+ if (phas_desc != NULL) *phas_desc = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (cli__entry_is_positional(entry)) {
+ if (pcount != NULL) {
+ (*pcount)++;
+ }
+ if (pmax_len != NULL) {
+ cli__max_len(entry->l, 0, pmax_len);
+ }
+ if (phas_desc != NULL) {
+ (*phas_desc)++;
+ }
+ } else {
+ if (count != NULL) {
+ (*count)++;
+ }
+ if (max_len != NULL) {
+ const char *type_str;
+ size_t type_len;
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+
+ cli__max_len(entry->l, type_len, max_len);
+ }
+ }
+ }
+}
+
+static inline bool cli__is_negative(const char *arg)
+{
+ int64_t i;
+ size_t pos = 0;
+
+ return cli__parse_value_int(arg, &i, &pos)
+ && pos == strlen(arg)
+ && i < 0;
+}
+
+/* Documented in cli.h */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
+{
+ size_t pos_count = 0;
+ enum {
+ ARG_PROG_NAME,
+ ARG_FIRST,
+ };
+
+ for (int i = ARG_FIRST; i < argc; i++) {
+ const char *arg = argv[i];
+ size_t pos_inc = 0;
+ bool ret;
+
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ ret = cli__parse_long(cli, argc, argv, &i);
+ } else {
+ ret = cli__parse_short(cli, argc, argv, &i);
+ if (ret != true) {
+ if (cli__is_negative(argv[i])) {
+ pos_inc = 1;
+ ret = cli__parse_positional(
+ cli, argv[i],
+ pos_count);
+ }
+ }
+ }
+ } else {
+ pos_inc = 1;
+ ret = cli__parse_positional(cli, argv[i], pos_count);
+ }
+
+ if (ret != true) {
+ return ret;
+ }
+
+ pos_count += pos_inc;
+ }
+
+ if (pos_count < cli->min_positional) {
+ fprintf(stderr, "Insufficient positional arguments found.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Get terminal width.
+ *
+ * \return terminal width in characters.
+ */
+static size_t cli__terminal_width(void)
+{
+ return 80;
+}
+
+/**
+ * Print an entry's description, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] entry The entry to print the description for.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_description(const struct cli_table_entry *entry,
+ size_t indent)
+{
+ size_t terminal_width = cli__terminal_width();
+ size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
+ size_t space = avail;
+ const char *desc = entry->d;
+
+ if (desc != NULL) {
+ while (*desc != '\0') {
+ size_t word_len = strcspn(desc, " \n\t");
+ if (word_len <= space || space == avail) {
+ fprintf(stderr, "%*.*s",
+ (int)word_len,
+ (int)word_len, desc);
+ desc += word_len;
+ if (word_len <= space) {
+ space -= word_len;
+ }
+ if (space > 0) {
+ fprintf(stderr, " ");
+ space--;
+ }
+ } else {
+ fprintf(stderr, "\n%*s", (int)indent, "");
+ space = avail;
+ }
+ desc += strspn(desc, " \n\t");
+ }
+ }
+
+ fprintf(stderr, "\n");
+}
+
+/* Documented in cli.h */
+void cli_help(const struct cli_table *cli, const char *prog_name)
+{
+ size_t count;
+ size_t pcount;
+ size_t max_len;
+ size_t pmax_len;
+ size_t phas_desc;
+ size_t required = 0;
+ enum {
+ ARG_PROG_NAME,
+ };
+
+ cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
+
+ fprintf(stderr, "\nUsage: %s", prog_name);
+
+ if (pcount > 0) {
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ const char *punctuation =
+ (required == cli->min_positional) ?
+ " [" : " ";
+
+ if (cli->entries[i].t == CLI_CMD) {
+ fprintf(stderr, "%s%s", punctuation,
+ cli->entries[i].l);
+ } else {
+ fprintf(stderr, "%s<%s>", punctuation,
+ cli->entries[i].l);
+ }
+ required++;
+ }
+ }
+ if (required == pcount && required > cli->min_positional) {
+ fprintf(stderr, "]");
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, " [options]");
+ }
+
+ fprintf(stderr, "\n\n");
+
+ if (phas_desc > 0) {
+ fprintf(stderr, "Where:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (entry->d == NULL) {
+ continue;
+ }
+
+ if (cli__entry_is_positional(entry)) {
+ fprintf(stderr, " %*.*s ",
+ (int)pmax_len,
+ (int)pmax_len,
+ entry->l);
+ cli__print_description(entry, pmax_len + 4);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, "Options:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+ const char *type_str;
+ size_t type_len;
+ size_t arg_len;
+
+ if (cli__entry_is_positional(entry)) {
+ continue;
+ }
+
+ if (entry->s != '\0') {
+ fprintf(stderr, " -%c", entry->s);
+ } else {
+ fprintf(stderr, " ");
+ }
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+ arg_len = strlen(entry->l);
+
+ fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
+ (int)(max_len - arg_len - type_len),
+ "");
+ cli__print_description(entry, max_len + 11);
+ fprintf(stderr, "\n");
+ }
+ }
+}
diff --git a/test/cli.h b/test/cli.h
new file mode 100644
index 0000000..91db086
--- /dev/null
+++ b/test/cli.h
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling API.
+ */
+
+#ifndef _PELTAR_CLI_H_
+#define _PELTAR_CLI_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Helper to get element count for an array,
+ *
+ * \param[in] _a Array to get number of elements for.
+ */
+#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
+
+/**
+ * CLI argument type.
+ */
+enum cli_arg_type {
+ CLI_CMD, /**< A sub-command. Must match long argument name. */
+ CLI_BOOL, /**< Has no value; presence of flag indicates true. */
+ CLI_INT, /**< Has signed integer value. */
+ CLI_UINT, /**< Has unsigned integer value. */
+ CLI_ENUM, /**< Has enumeration value. */
+ CLI_STRING, /**< Has string value. */
+};
+
+struct cli_str_val {
+ const char *str;
+ int64_t val;
+};
+
+struct cli_enum {
+ const struct cli_str_val *desc;
+ int64_t *e; /**< Location to store \ref CLI_ENUM value. */
+};
+
+/**
+ * Client description for a command line argument.
+ */
+struct cli_table_entry {
+ const char *l; /**< Long argument name. */
+ const char s; /**< Short flag name. (Non-positional arguments.) */
+ bool p; /**< Whether the argument is a positional argument. */
+ enum cli_arg_type t; /**< Argument type. */
+ union {
+ bool *b; /**< Location to store \ref CLI_BOOL value. */
+ int64_t *i; /**< Location to store \ref CLI_INT value. */
+ uint64_t *u; /**< Location to store \ref CLI_UINT value. */
+ const char **s; /**< Location to store \ref CLI_STRING value. */
+ struct cli_enum e;
+ } v; /**< Where to store type-specific values. */
+ const char *d; /**< Description. */
+};
+
+/**
+ * Client command line interface specification.
+ */
+struct cli_table {
+ const struct cli_table_entry *entries;
+ size_t count;
+ size_t min_positional;
+};
+
+/**
+ * Parse the command line arguments.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \return true on success, false on error.
+ */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
+
+/**
+ * Print usage and help output.
+ *
+ * Note: Assumes non-Unicode. (One byte per character.)
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] prog_name Program name.
+ */
+void cli_help(const struct cli_table *cli, const char *prog_name);
+
+#endif
diff --git a/test/nsgif.c b/test/nsgif.c
index 173f70c..94e7960 100644
--- a/test/nsgif.c
+++ b/test/nsgif.c
@@ -17,8 +17,56 @@
#include "../include/nsgif.h"
+#include "cli.h"
+#include "cli.c"
+
#define BYTES_PER_PIXEL 4
+static struct nsgif_options {
+ const char *file;
+ const char *ppm;
+ uint64_t loops;
+ bool info;
+} nsgif_options;
+
+static const struct cli_table_entry cli_entries[] = {
+ {
+ .s = 'm',
+ .l = "ppm",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.ppm,
+ .d = "Convert frames to PPM image at given path."
+ },
+ {
+ .s = 'i',
+ .l = "info",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.info,
+ .d = "Dump GIF info to stdout."
+ },
+ {
+ .s = 'l',
+ .l = "loops",
+ .t = CLI_UINT,
+ .v.u = &nsgif_options.loops,
+ .d = "Loop through decoding all frames N times. "
+ "The default is 1."
+ },
+ {
+ .p = true,
+ .l = "FILE",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.file,
+ .d = "Path to GIF file to load."
+ },
+};
+
+const struct cli_table cli = {
+ .entries = cli_entries,
+ .count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
+ .min_positional = 1,
+};
+
static void *bitmap_create(int width, int height)
{
/* Ensure a stupidly large bitmap is not created */
@@ -84,7 +132,31 @@ static void warning(const char *context, nsgif_error err)
context, nsgif_strerror(err));
}
-static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
+static void print_gif_info(const nsgif_info_t *info)
+{
+ fprintf(stdout, "gif:\n");
+ fprintf(stdout, " width: %"PRIu32"\n", info->width);
+ fprintf(stdout, " height: %"PRIu32"\n", info->height);
+ fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
+ fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
+ fprintf(stdout, " frames:\n");
+}
+
+static void print_gif_frame_info(const nsgif_frame_info_t *info)
+{
+ const char *disposal = nsgif_str_disposal(info->disposal);
+
+ fprintf(stdout, " - disposal-method: %s\n", disposal);
+ fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
+ fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
+ fprintf(stdout, " rect:\n");
+ fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
+ fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
+ fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
+ fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
+}
+
+static void decode(FILE* ppm, const char *name, nsgif *gif)
{
nsgif_error err;
uint32_t frame_prev = 0;
@@ -92,17 +164,21 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
info = nsgif_get_info(gif);
- if (write_ppm) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", info->width);
- fprintf(fh, "# height %u \n", info->height);
- fprintf(fh, "# frame_count %u \n", info->frame_count);
- fprintf(fh, "# loop_max %u \n", info->loop_max);
- fprintf(fh, "%u %u 256\n", info->width,
+ if (ppm != NULL) {
+ fprintf(ppm, "P3\n");
+ fprintf(ppm, "# %s\n", name);
+ fprintf(ppm, "# width %u \n", info->width);
+ fprintf(ppm, "# height %u \n", info->height);
+ fprintf(ppm, "# frame_count %u \n", info->frame_count);
+ fprintf(ppm, "# loop_max %u \n", info->loop_max);
+ fprintf(ppm, "%u %u 256\n", info->width,
info->height * info->frame_count);
}
+ if (nsgif_options.info == true) {
+ print_gif_info(info);
+ }
+
/* decode the frames */
while (true) {
nsgif_bitmap_t *buffer;
@@ -131,20 +207,32 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
return;
}
- if (write_ppm) {
- fprintf(fh, "# frame %u:\n", frame_new);
+ if (nsgif_options.info == true) {
+ const nsgif_frame_info_t *f_info;
+
+ f_info = nsgif_get_frame_info(gif, frame_new);
+ assert(f_info != NULL);
+ print_gif_frame_info(f_info);
+ }
+
+ if (ppm != NULL) {
+ fprintf(ppm, "# frame %u:\n", frame_new);
image = (const uint8_t *) buffer;
for (uint32_t y = 0; y != info->height; y++) {
for (uint32_t x = 0; x != info->width; x++) {
size_t z = (y * info->width + x) * 4;
- fprintf(fh, "%u %u %u ",
+ fprintf(ppm, "%u %u %u ",
image[z],
image[z + 1],
image[z + 2]);
}
- fprintf(fh, "\n");
+ fprintf(ppm, "\n");
}
}
+
+ if (delay_cs == NSGIF_INFINITE) {
+ return;
+ }
}
}
@@ -159,40 +247,32 @@ int main(int argc, char *argv[])
size_t size;
uint8_t *data;
nsgif_error err;
- FILE *outf = stdout;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
+ FILE *ppm = NULL;
+
+ /* Override default options with any command line args */
+ if (!cli_parse(&cli, argc, (void *)argv)) {
+ cli_help(&cli, argv[0]);
+ return EXIT_FAILURE;
}
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
+ if (nsgif_options.ppm != NULL) {
+ ppm = fopen(nsgif_options.ppm, "w+");
+ if (ppm == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ nsgif_options.ppm);
+ return EXIT_FAILURE;
}
}
/* create our gif animation */
err = nsgif_create(&bitmap_callbacks, &gif);
if (err != NSGIF_OK) {
- return 1;
+ warning("nsgif_create", err);
+ return EXIT_FAILURE;
}
/* load file into memory */
- data = load_file(argv[1], &size);
+ data = load_file(nsgif_options.file, &size);
/* Scan the raw data */
err = nsgif_data_scan(gif, size, data);
@@ -200,13 +280,15 @@ int main(int argc, char *argv[])
warning("nsgif_data_scan", err);
nsgif_destroy(gif);
free(data);
- return 1;
+ return EXIT_FAILURE;
}
- decode(outf, argv[1], gif, !no_write);
+ for (uint64_t i = 0; i < nsgif_options.loops; i++) {
+ decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
+ }
- if (argc > 2 && !no_write) {
- fclose(outf);
+ if (ppm != NULL) {
+ fclose(ppm);
}
/* clean up */
diff --git a/test/runtest.sh b/test/runtest.sh
index 47cec7e..fd84847 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -23,7 +23,7 @@ gifdecode()
OUTF=$(basename ${1} .gif)
CMPF=$(dirname ${1})/${OUTF}.ppm
echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_nsgif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} --ppm ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
ECODE=$?
echo "Exit code:${ECODE}" >> ${TEST_LOG}
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=370c2a783b67350143...
commit 370c2a783b67350143c18a07463835887d2b1847
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
API: Add public access function for frame information.
diff --git a/include/nsgif.h b/include/nsgif.h
index 550fea3..b36af8a 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -20,22 +20,29 @@
#include <stdbool.h>
#include <inttypes.h>
+/** Representation of infinity. */
#define NSGIF_INFINITE (UINT32_MAX)
typedef struct nsgif nsgif;
-typedef struct nsgif_info {
- /** width of GIF (may increase during decoding) */
- uint32_t width;
- /** height of GIF (may increase during decoding) */
- uint32_t height;
- /** number of frames decoded */
- uint32_t frame_count;
- /** number of times to loop animation */
- int loop_max;
- /** number of animation loops so far */
- int loop_count;
-} nsgif_info_t;
+/**
+ * GIF rectangle structure.
+ *
+ * * Top left coordinate is `(x0, y0)`.
+ * * Width is `x1 - x0`.
+ * * Height is `y1 - y0`.
+ * * Units are pixels.
+ */
+typedef struct nsgif_rect {
+ /** x co-ordinate of redraw rectangle, left */
+ uint32_t x0;
+ /** y co-ordinate of redraw rectangle, top */
+ uint32_t y0;
+ /** x co-ordinate of redraw rectangle, right */
+ uint32_t x1;
+ /** y co-ordinate of redraw rectangle, bottom */
+ uint32_t y1;
+} nsgif_rect;
/**
* NSGIF return codes.
@@ -94,25 +101,6 @@ typedef enum {
} nsgif_error;
/**
- * GIF rectangle structure.
- *
- * * Top left coordinate is `(x0, y0)`.
- * * Width is `x1 - x0`.
- * * Height is `y1 - y0`.
- * * Units are pixels.
- */
-typedef struct nsgif_rect {
- /** x co-ordinate of redraw rectangle, left */
- uint32_t x0;
- /** y co-ordinate of redraw rectangle, top */
- uint32_t y0;
- /** x co-ordinate of redraw rectangle, right */
- uint32_t x1;
- /** y co-ordinate of redraw rectangle, bottom */
- uint32_t y1;
-} nsgif_rect;
-
-/**
* Client bitmap type.
*
* These are client-created and destroyed, via the \ref bitmap callbacks,
@@ -193,6 +181,13 @@ const char *nsgif_strerror(nsgif_error err);
nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
/**
+ * Free a NSGIF object.
+ *
+ * \param[in] gif The NSGIF to free.
+ */
+void nsgif_destroy(nsgif *gif);
+
+/**
* Scan the source image data.
*
* This is used to feed the source data into LibNSGIF. This must be called
@@ -224,6 +219,10 @@ nsgif_error nsgif_data_scan(
/**
* Prepare to show a frame.
*
+ * If this is the last frame of an animation with a finite loop count, the
+ * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
+ * should be shown forever.
+ *
* \param[in] gif The NSGIF object.
* \param[out] area The area in pixels that must be redrawn.
* \param[out] delay_cs Time to wait after frame_new before next frame in cs.
@@ -268,6 +267,60 @@ nsgif_error nsgif_reset(
nsgif *gif);
/**
+ * Information about a GIF.
+ */
+typedef struct nsgif_info {
+ /** width of GIF (may increase during decoding) */
+ uint32_t width;
+ /** height of GIF (may increase during decoding) */
+ uint32_t height;
+ /** number of frames decoded */
+ uint32_t frame_count;
+ /** number of times to loop animation */
+ int loop_max;
+ /** number of animation loops so far */
+ int loop_count;
+} nsgif_info_t;
+
+/**
+ * Frame disposal method.
+ *
+ * Clients do not need to know about this, it is provided purely for dumping
+ * raw information about GIF frames.
+ */
+enum nsgif_disposal {
+ NSGIF_DISPOSAL_UNSPECIFIED, /**< No disposal method specified. */
+ NSGIF_DISPOSAL_NONE, /**< Frame remains. */
+ NSGIF_DISPOSAL_RESTORE_BG, /**< Clear frame to background colour. */
+ NSGIF_DISPOSAL_RESTORE_PREV, /**< Restore previous frame. */
+ NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
+};
+
+/**
+ * Convert a disposal method to a string.
+ *
+ * \param[in] disposal The disposal method to convert.
+ * \return String representation of given disposal method.
+ */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal);
+
+/**
+ * Information about a GIF frame.
+ */
+typedef struct nsgif_frame_info {
+ /** whether the frame should be displayed/animated */
+ bool display;
+
+ /** Disposal method for previous frame; affects plotting */
+ uint8_t disposal;
+ /** delay (in cs) before animating the frame */
+ uint32_t delay;
+
+ /** Frame's redraw rectangle. */
+ nsgif_rect rect;
+} nsgif_frame_info_t;
+
+/**
* Get information about a GIF from an NSGIF object.
*
* \param[in] gif The NSGIF object to get info for.
@@ -277,10 +330,15 @@ nsgif_error nsgif_reset(
const nsgif_info_t *nsgif_get_info(const nsgif *gif);
/**
- * Free a NSGIF object.
+ * Get information about a GIF from an NSGIF object.
*
- * \param[in] gif The NSGIF to free.
+ * \param[in] gif The NSGIF object to get frame info for.
+ * \param[in] frame The frame number to get info for.
+ *
+ * \return The gif frame info, or NULL on error.
*/
-void nsgif_destroy(nsgif *gif);
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif *gif,
+ uint32_t frame);
#endif
diff --git a/src/gif.c b/src/gif.c
index 02126ca..a5f059e 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -22,12 +22,7 @@
/** GIF frame data */
typedef struct nsgif_frame {
- /** whether the frame should be displayed/animated */
- bool display;
- /** delay (in cs) before animating the frame */
- uint32_t frame_delay;
-
- /* Internal members are listed below */
+ struct nsgif_frame_info info;
/** offset (in bytes) to the GIF frame data */
uint32_t frame_pointer;
@@ -37,17 +32,14 @@ typedef struct nsgif_frame {
bool opaque;
/** whether a full image redraw is required */
bool redraw_required;
- /** how the previous frame should be disposed; affects plotting */
- uint8_t disposal_method;
+
/** whether we acknowledge transparency */
bool transparency;
/** the index designating a transparent pixel */
uint32_t transparency_index;
+
/* Frame flags */
uint32_t flags;
-
- /** Frame's redraw rectangle. */
- nsgif_rect redraw;
} nsgif_frame;
/** GIF animation data */
@@ -137,14 +129,6 @@ struct nsgif {
/** No transparency */
#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu)
-enum nsgif_disposal {
- NSGIF_DISPOSAL_UNSPECIFIED,
- NSGIF_DISPOSAL_NONE,
- NSGIF_DISPOSAL_RESTORE_BG,
- NSGIF_DISPOSAL_RESTORE_PREV,
- NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
-};
-
/* GIF Flags */
#define NSGIF_COLOUR_TABLE_MASK 0x80
#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07
@@ -578,10 +562,10 @@ static inline nsgif_error nsgif__decode(
};
nsgif_error ret;
- uint32_t width = frame->redraw.x1 - frame->redraw.x0;
- uint32_t height = frame->redraw.y1 - frame->redraw.y0;
- uint32_t offset_x = frame->redraw.x0;
- uint32_t offset_y = frame->redraw.y0;
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
uint32_t interlace = frame->flags & GIF_MASK_INTERLACE;
uint32_t transparency_index = frame->transparency_index;
uint32_t *restrict colour_table = gif->colour_table;
@@ -616,15 +600,15 @@ static void nsgif__restore_bg(
memset(bitmap, NSGIF_TRANSPARENT_COLOUR,
gif->info.width * gif->info.height * sizeof(*bitmap));
} else {
- uint32_t width = frame->redraw.x1 - frame->redraw.x0;
- uint32_t height = frame->redraw.y1 - frame->redraw.y0;
- uint32_t offset_x = frame->redraw.x0;
- uint32_t offset_y = frame->redraw.y0;
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
width -= gif__clip(offset_x, width, gif->info.width);
height -= gif__clip(offset_y, height, gif->info.height);
- if (frame->display == false || width == 0) {
+ if (frame->info.display == false || width == 0) {
return;
}
@@ -671,10 +655,10 @@ static nsgif_error nsgif__update_bitmap(
} else {
struct nsgif_frame *prev = &gif->frames[frame_idx - 1];
- if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_BG) {
+ if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_BG) {
nsgif__restore_bg(gif, prev, bitmap);
- } else if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) {
+ } else if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
ret = nsgif__recover_frame(gif, bitmap);
if (ret != NSGIF_OK) {
nsgif__restore_bg(gif, prev, bitmap);
@@ -682,7 +666,7 @@ static nsgif_error nsgif__update_bitmap(
}
}
- if (frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) {
+ if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
/* Store the previous frame for later restoration */
nsgif__record_frame(gif, bitmap);
}
@@ -737,9 +721,9 @@ static nsgif_error nsgif__parse_extension_graphic_control(
return NSGIF_ERR_END_OF_DATA;
}
- frame->frame_delay = data[3] | (data[4] << 8);
- if (frame->frame_delay < gif->delay_min) {
- frame->frame_delay = gif->delay_default;
+ frame->info.delay = data[3] | (data[4] << 8);
+ if (frame->info.delay < gif->delay_min) {
+ frame->info.delay = gif->delay_default;
}
if (data[2] & GIF_MASK_TRANSPARENCY) {
@@ -747,22 +731,22 @@ static nsgif_error nsgif__parse_extension_graphic_control(
frame->transparency_index = data[5];
}
- frame->disposal_method = ((data[2] & GIF_MASK_DISPOSAL) >> 2);
+ frame->info.disposal = ((data[2] & GIF_MASK_DISPOSAL) >> 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 == NSGIF_DISPOSAL_RESTORE_QUIRK) {
- frame->disposal_method = NSGIF_DISPOSAL_RESTORE_PREV;
+ if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->info.disposal = NSGIF_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 == NSGIF_DISPOSAL_RESTORE_BG ||
- frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV;
+ frame->info.disposal == NSGIF_DISPOSAL_RESTORE_BG ||
+ frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV;
return NSGIF_OK;
}
@@ -964,10 +948,10 @@ static nsgif_error nsgif__parse_image_descriptor(
h = data[7] | (data[8] << 8);
frame->flags = data[9];
- frame->redraw.x0 = x;
- frame->redraw.y0 = y;
- frame->redraw.x1 = x + w;
- frame->redraw.y1 = y + h;
+ frame->info.rect.x0 = x;
+ frame->info.rect.y0 = y;
+ frame->info.rect.x1 = x + w;
+ frame->info.rect.y1 = y + h;
/* Allow first frame to grow image dimensions. */
if (gif->info.frame_count == 0) {
@@ -1143,7 +1127,7 @@ static nsgif_error nsgif__parse_image_data(
*pos = data;
gif->info.frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
+ gif->frames[frame_idx].info.display = true;
return NSGIF_OK;
}
@@ -1177,9 +1161,9 @@ static struct nsgif_frame *nsgif__get_frame(
frame->transparency_index = NSGIF_NO_TRANSPARENCY;
frame->frame_pointer = gif->buf_pos;
frame->redraw_required = false;
- frame->disposal_method = 0;
- frame->frame_delay = 100;
- frame->display = false;
+ frame->info.display = false;
+ frame->info.disposal = 0;
+ frame->info.delay = 10;
frame->decoded = false;
}
@@ -1215,7 +1199,7 @@ static nsgif_error nsgif__process_frame(
pos = gif->buf + frame->frame_pointer;
/* Ensure this frame is supposed to be decoded */
- if (frame->display == false) {
+ if (frame->info.display == false) {
return NSGIF_OK;
}
@@ -1592,10 +1576,10 @@ static nsgif_error nsgif__next_displayable_frame(
}
if (delay != NULL) {
- *delay += gif->frames[next].frame_delay;
+ *delay += gif->frames[next].info.delay;
}
- } while (gif->frames[next].display == false);
+ } while (gif->frames[next].info.display == false);
*frame = next;
return NSGIF_OK;
@@ -1637,8 +1621,8 @@ nsgif_error nsgif_frame_prepare(
if (gif->frame != NSGIF_FRAME_INVALID &&
gif->frame < gif->info.frame_count &&
- gif->frames[gif->frame].display) {
- rect = gif->frames[gif->frame].redraw;
+ gif->frames[gif->frame].info.display) {
+ rect = gif->frames[gif->frame].info.rect;
}
if (nsgif__animation_complete(
@@ -1671,7 +1655,7 @@ nsgif_error nsgif_frame_prepare(
}
gif->frame = frame;
- nsgif__redraw_rect_extend(&gif->frames[frame].redraw, &rect);
+ nsgif__redraw_rect_extend(&gif->frames[frame].info.rect, &rect);
*frame_new = gif->frame;
*delay_cs = delay;
@@ -1717,12 +1701,25 @@ nsgif_error nsgif_frame_decode(
return ret;
}
+/* exported function documented in nsgif.h */
const nsgif_info_t *nsgif_get_info(const nsgif *gif)
{
return &gif->info;
}
/* exported function documented in nsgif.h */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif *gif,
+ uint32_t frame)
+{
+ if (frame > gif->info.frame_count) {
+ return NULL;
+ }
+
+ return &gif->frames[frame].info;
+}
+
+/* exported function documented in nsgif.h */
const char *nsgif_strerror(nsgif_error err)
{
static const char *const str[] = {
@@ -1744,3 +1741,21 @@ const char *nsgif_strerror(nsgif_error err)
return str[err];
}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal)
+{
+ static const char *const str[] = {
+ [NSGIF_DISPOSAL_UNSPECIFIED] = "Unspecified",
+ [NSGIF_DISPOSAL_NONE] = "None",
+ [NSGIF_DISPOSAL_RESTORE_BG] = "Restore background",
+ [NSGIF_DISPOSAL_RESTORE_PREV] = "Restore previous",
+ [NSGIF_DISPOSAL_RESTORE_QUIRK] = "Restore quirk",
+ };
+
+ if (disposal >= NSGIF_ARRAY_LEN(str) || str[disposal] == NULL) {
+ return "Unspecified";
+ }
+
+ return str[disposal];
+}
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=84a9edb126121cd145...
commit 84a9edb126121cd145295e48a5f0a98de4aab708
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Rename gif source data buffer members.
diff --git a/src/gif.c b/src/gif.c
index b8af0b3..02126ca 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -58,8 +58,6 @@ struct nsgif {
void *lzw_ctx;
/** callbacks for bitmap functions */
nsgif_bitmap_cb_vt bitmap;
- /** pointer to GIF data */
- const uint8_t *nsgif_data;
/** decoded frames */
nsgif_frame *frames;
/** current frame */
@@ -75,10 +73,13 @@ struct nsgif {
/** number of frames partially decoded */
uint32_t frame_count_partial;
+ /** pointer to GIF data */
+ const uint8_t *buf;
/** current index into GIF data */
- uint32_t buffer_position;
+ uint32_t buf_pos;
/** total number of bytes of GIF data available */
- uint32_t buffer_size;
+ uint32_t buf_len;
+
/** current number of frame holders */
uint32_t frame_holders;
/** background index */
@@ -445,8 +446,8 @@ static nsgif_error nsgif__decode_complex(
/* Initialise the LZW decoding */
res = lzw_decode_init(gif->lzw_ctx, data[0],
- gif->nsgif_data, gif->buffer_size,
- data + 1 - gif->nsgif_data);
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
if (res != LZW_OK) {
return nsgif__error_from_lzw(res);
}
@@ -535,8 +536,8 @@ static nsgif_error nsgif__decode_simple(
/* Initialise the LZW decoding */
res = lzw_decode_init_map(gif->lzw_ctx, data[0],
transparency_index, colour_table,
- gif->nsgif_data, gif->buffer_size,
- data + 1 - gif->nsgif_data);
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
if (res != LZW_OK) {
return nsgif__error_from_lzw(res);
}
@@ -825,7 +826,7 @@ static nsgif_error nsgif__parse_frame_extensions(
GIF_EXT_APPLICATION = 0xff,
};
const uint8_t *nsgif_data = *pos;
- const uint8_t *nsgif_end = gif->nsgif_data + gif->buffer_size;
+ const uint8_t *nsgif_end = gif->buf + gif->buf_len;
int nsgif_bytes = nsgif_end - nsgif_data;
/* Initialise the extensions */
@@ -937,7 +938,7 @@ static nsgif_error nsgif__parse_image_descriptor(
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
enum {
NSGIF_IMAGE_DESCRIPTOR_LEN = 10u,
NSGIF_IMAGE_SEPARATOR = 0x2Cu,
@@ -1001,7 +1002,7 @@ static nsgif_error nsgif__colour_table_extract(
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < colour_table_entries * 3) {
return NSGIF_ERR_END_OF_DATA;
@@ -1087,7 +1088,7 @@ static nsgif_error nsgif__parse_image_data(
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
uint32_t frame_idx = frame - gif->frames;
uint8_t minimum_code_size;
nsgif_error ret;
@@ -1174,7 +1175,7 @@ static struct nsgif_frame *nsgif__get_frame(
frame->transparency = false;
frame->transparency_index = NSGIF_NO_TRANSPARENCY;
- frame->frame_pointer = gif->buffer_position;
+ frame->frame_pointer = gif->buf_pos;
frame->redraw_required = false;
frame->disposal_method = 0;
frame->frame_delay = 100;
@@ -1208,10 +1209,10 @@ static nsgif_error nsgif__process_frame(
return NSGIF_ERR_OOM;
}
- end = gif->nsgif_data + gif->buffer_size;
+ end = gif->buf + gif->buf_len;
if (decode) {
- pos = gif->nsgif_data + frame->frame_pointer;
+ pos = gif->buf + frame->frame_pointer;
/* Ensure this frame is supposed to be decoded */
if (frame->display == false) {
@@ -1228,7 +1229,7 @@ static nsgif_error nsgif__process_frame(
return NSGIF_OK;
}
} else {
- pos = (uint8_t *)(gif->nsgif_data + gif->buffer_position);
+ pos = (uint8_t *)(gif->buf + gif->buf_pos);
/* Check if we've finished */
if (pos < end && pos[0] == NSGIF_TRAILER) {
@@ -1265,7 +1266,7 @@ static nsgif_error nsgif__process_frame(
cleanup:
if (!decode) {
- gif->buffer_position = pos - gif->nsgif_data;
+ gif->buf_pos = pos - gif->buf;
}
return ret;
@@ -1337,7 +1338,7 @@ static nsgif_error nsgif__parse_header(
bool strict)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < 6) {
return NSGIF_ERR_END_OF_DATA;
@@ -1384,7 +1385,7 @@ static nsgif_error nsgif__parse_logical_screen_descriptor(
const uint8_t **pos)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < 7) {
return NSGIF_ERR_END_OF_DATA;
@@ -1413,14 +1414,14 @@ nsgif_error nsgif_data_scan(
uint32_t frames;
/* Initialize values */
- gif->buffer_size = size;
- gif->nsgif_data = data;
+ gif->buf_len = size;
+ gif->buf = data;
/* Get our current processing position */
- nsgif_data = gif->nsgif_data + gif->buffer_position;
+ nsgif_data = gif->buf + gif->buf_pos;
/* See if we should initialise the GIF */
- if (gif->buffer_position == 0) {
+ if (gif->buf_pos == 0) {
/* We want everything to be NULL before we start so we've no
* chance of freeing bad pointers (paranoia)
*/
@@ -1445,7 +1446,7 @@ nsgif_error nsgif_data_scan(
}
/* Remember we've done this now */
- gif->buffer_position = nsgif_data - gif->nsgif_data;
+ gif->buf_pos = nsgif_data - gif->buf;
/* Some broken GIFs report the size as the screen size they
* were created in. As such, we detect for the common cases and
@@ -1473,7 +1474,7 @@ nsgif_error nsgif_data_scan(
* termination block) Although generally useless, the GIF
* specification does not expressly prohibit this
*/
- if (gif->buffer_size == gif->buffer_position + 1) {
+ if (gif->buf_len == gif->buf_pos + 1) {
if (nsgif_data[0] == NSGIF_TRAILER) {
return NSGIF_OK;
}
@@ -1495,7 +1496,7 @@ nsgif_error nsgif_data_scan(
return ret;
}
- gif->buffer_position = (nsgif_data - gif->nsgif_data);
+ gif->buf_pos = (nsgif_data - gif->buf);
} else {
/* Create a default colour table with the first two
* colours as black and white
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=ff86c40667cdaa2a53...
commit ff86c40667cdaa2a535c992f908ba15912d93d59
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Minimum gif delay shouldn't be public.
diff --git a/include/nsgif.h b/include/nsgif.h
index f03d121..550fea3 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -35,8 +35,6 @@ typedef struct nsgif_info {
int loop_max;
/** number of animation loops so far */
int loop_count;
-
- uint16_t delay_min;
} nsgif_info_t;
/**
diff --git a/src/gif.c b/src/gif.c
index 3374b1e..b8af0b3 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -69,7 +69,9 @@ struct nsgif {
/** currently decoded image; stored as bitmap from bitmap_create callback */
nsgif_bitmap_t *frame_image;
+ uint16_t delay_min;
uint16_t delay_default;
+
/** number of frames partially decoded */
uint32_t frame_count_partial;
@@ -735,7 +737,7 @@ static nsgif_error nsgif__parse_extension_graphic_control(
}
frame->frame_delay = data[3] | (data[4] << 8);
- if (frame->frame_delay < gif->info.delay_min) {
+ if (frame->frame_delay < gif->delay_min) {
frame->frame_delay = gif->delay_default;
}
@@ -1309,7 +1311,7 @@ nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
gif->decoded_frame = NSGIF_FRAME_INVALID;
gif->prev_index = NSGIF_FRAME_INVALID;
- gif->info.delay_min = 2;
+ gif->delay_min = 2;
gif->delay_default = 10;
*gif_out = gif;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=a0b9fcb4fa4c533197...
commit a0b9fcb4fa4c53319726044c8278a551293af817
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Test: Use tabs for indent in test runner script.
Previously it was 4 spaces for first level indent, 1 tab for second
level, 1 tab plus 4 spaces for third level.
diff --git a/test/runtest.sh b/test/runtest.sh
index 5c22709..47cec7e 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh
# run test images through libnsgif and count results
@@ -20,25 +20,25 @@ GIFTESTS="${GIFTESTS} test/ns-afl-gif/*.gif"
gifdecode()
{
- OUTF=$(basename ${1} .gif)
- CMPF=$(dirname ${1})/${OUTF}.ppm
- echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_nsgif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
- ECODE=$?
-
- echo "Exit code:${ECODE}" >> ${TEST_LOG}
- if [ "${ECODE}" -gt 0 ];then
- return ${ECODE}
- fi
-
- if [ -f "${CMPF}" ]; then
- cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
- if [ "$?" -ne 0 ]; then
- return 128
+ OUTF=$(basename ${1} .gif)
+ CMPF=$(dirname ${1})/${OUTF}.ppm
+ echo "GIF:${1}" >> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ECODE=$?
+
+ echo "Exit code:${ECODE}" >> ${TEST_LOG}
+ if [ "${ECODE}" -gt 0 ];then
+ return ${ECODE}
fi
- fi
- return 0
+ if [ -f "${CMPF}" ]; then
+ cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
+ if [ "$?" -ne 0 ]; then
+ return 128
+ fi
+ fi
+
+ return 0
}
GIFTESTTOTC=0
@@ -49,27 +49,27 @@ GIFTESTERRC=0
echo "Testing GIF decode"
for GIF in $(ls ${GIFTESTS});do
- GIFTESTTOTC=$((GIFTESTTOTC+1))
- #echo "${GIF}"
- gifdecode ${GIF}
- ECODE=$?
- if [ "${ECODE}" -gt 127 ];then
- GIFTESTERRC=$((GIFTESTERRC+1))
- echo "Error ${GIF}"
- else
- if [ "${ECODE}" -gt 0 ];then
- GIFTESTFAILC=$((GIFTESTFAILC+1))
+ GIFTESTTOTC=$((GIFTESTTOTC+1))
+ #echo "${GIF}"
+ gifdecode ${GIF}
+ ECODE=$?
+ if [ "${ECODE}" -gt 127 ];then
+ GIFTESTERRC=$((GIFTESTERRC+1))
+ echo "Error ${GIF}"
else
- GIFTESTPASSC=$((GIFTESTPASSC+1))
+ if [ "${ECODE}" -gt 0 ];then
+ GIFTESTFAILC=$((GIFTESTFAILC+1))
+ else
+ GIFTESTPASSC=$((GIFTESTPASSC+1))
+ fi
fi
- fi
done
echo "Tests:${GIFTESTTOTC} Pass:${GIFTESTPASSC} Fail:${GIFTESTFAILC} Error:${GIFTESTERRC}"
# exit code
if [ "${GIFTESTERRC}" -gt 0 ]; then
- exit 1
+ exit 1
fi
exit 0
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=dde30d3d4134c46439...
commit dde30d3d4134c46439fb1984eeb88ac2d843fd60
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
Test: Rename nsgif test utilitiy.
diff --git a/test/Makefile b/test/Makefile
index f067d81..a578aef 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,3 +1,3 @@
-DIR_TEST_ITEMS := decode_gif:decode_gif.c
+DIR_TEST_ITEMS := nsgif:nsgif.c
include $(NSBUILD)/Makefile.subdir
diff --git a/test/decode_gif.c b/test/decode_gif.c
deleted file mode 100644
index 173f70c..0000000
--- a/test/decode_gif.c
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#include "../include/nsgif.h"
-
-#define BYTES_PER_PIXEL 4
-
-static void *bitmap_create(int width, int height)
-{
- /* Ensure a stupidly large bitmap is not created */
- if (width > 4096 || height > 4096) {
- return NULL;
- }
-
- return calloc(width * height, BYTES_PER_PIXEL);
-}
-
-static unsigned char *bitmap_get_buffer(void *bitmap)
-{
- return bitmap;
-}
-
-static void bitmap_destroy(void *bitmap)
-{
- free(bitmap);
-}
-
-static uint8_t *load_file(const char *path, size_t *data_size)
-{
- FILE *fd;
- struct stat sb;
- unsigned char *buffer;
- size_t size;
- size_t n;
-
- fd = fopen(path, "rb");
- if (!fd) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- if (stat(path, &sb)) {
- perror(path);
- exit(EXIT_FAILURE);
- }
- size = sb.st_size;
-
- buffer = malloc(size);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %lld bytes\n",
- (long long) size);
- exit(EXIT_FAILURE);
- }
-
- n = fread(buffer, 1, size, fd);
- if (n != size) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- fclose(fd);
-
- *data_size = size;
- return buffer;
-}
-
-static void warning(const char *context, nsgif_error err)
-{
- fprintf(stderr, "%s failed: %s\n",
- context, nsgif_strerror(err));
-}
-
-static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
-{
- nsgif_error err;
- uint32_t frame_prev = 0;
- const nsgif_info_t *info;
-
- info = nsgif_get_info(gif);
-
- if (write_ppm) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", info->width);
- fprintf(fh, "# height %u \n", info->height);
- fprintf(fh, "# frame_count %u \n", info->frame_count);
- fprintf(fh, "# loop_max %u \n", info->loop_max);
- fprintf(fh, "%u %u 256\n", info->width,
- info->height * info->frame_count);
- }
-
- /* decode the frames */
- while (true) {
- nsgif_bitmap_t *buffer;
- const uint8_t *image;
- uint32_t frame_new;
- uint32_t delay_cs;
- nsgif_rect area;
-
- err = nsgif_frame_prepare(gif, &area,
- &delay_cs, &frame_new);
- if (err != NSGIF_OK) {
- warning("nsgif_frame_prepare", err);
- return;
- }
-
- if (frame_new < frame_prev) {
- /* Must be an animation that loops. We only care about
- * decoding each frame once. */
- return;
- }
- frame_prev = frame_new;
-
- err = nsgif_frame_decode(gif, frame_new, &buffer);
- if (err != NSGIF_OK) {
- warning("nsgif_decode_frame", err);
- return;
- }
-
- if (write_ppm) {
- fprintf(fh, "# frame %u:\n", frame_new);
- image = (const uint8_t *) buffer;
- for (uint32_t y = 0; y != info->height; y++) {
- for (uint32_t x = 0; x != info->width; x++) {
- size_t z = (y * info->width + x) * 4;
- fprintf(fh, "%u %u %u ",
- image[z],
- image[z + 1],
- image[z + 2]);
- }
- fprintf(fh, "\n");
- }
- }
- }
-}
-
-int main(int argc, char *argv[])
-{
- const nsgif_bitmap_cb_vt bitmap_callbacks = {
- .create = bitmap_create,
- .destroy = bitmap_destroy,
- .get_buffer = bitmap_get_buffer,
- };
- nsgif *gif;
- size_t size;
- uint8_t *data;
- nsgif_error err;
- FILE *outf = stdout;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
- }
-
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
- }
- }
-
- /* create our gif animation */
- err = nsgif_create(&bitmap_callbacks, &gif);
- if (err != NSGIF_OK) {
- return 1;
- }
-
- /* load file into memory */
- data = load_file(argv[1], &size);
-
- /* Scan the raw data */
- err = nsgif_data_scan(gif, size, data);
- if (err != NSGIF_OK) {
- warning("nsgif_data_scan", err);
- nsgif_destroy(gif);
- free(data);
- return 1;
- }
-
- decode(outf, argv[1], gif, !no_write);
-
- if (argc > 2 && !no_write) {
- fclose(outf);
- }
-
- /* clean up */
- nsgif_destroy(gif);
- free(data);
-
- return 0;
-}
diff --git a/test/nsgif.c b/test/nsgif.c
new file mode 100644
index 0000000..173f70c
--- /dev/null
+++ b/test/nsgif.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
+ * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "../include/nsgif.h"
+
+#define BYTES_PER_PIXEL 4
+
+static void *bitmap_create(int width, int height)
+{
+ /* Ensure a stupidly large bitmap is not created */
+ if (width > 4096 || height > 4096) {
+ return NULL;
+ }
+
+ return calloc(width * height, BYTES_PER_PIXEL);
+}
+
+static unsigned char *bitmap_get_buffer(void *bitmap)
+{
+ return bitmap;
+}
+
+static void bitmap_destroy(void *bitmap)
+{
+ free(bitmap);
+}
+
+static uint8_t *load_file(const char *path, size_t *data_size)
+{
+ FILE *fd;
+ struct stat sb;
+ unsigned char *buffer;
+ size_t size;
+ size_t n;
+
+ fd = fopen(path, "rb");
+ if (!fd) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(path, &sb)) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+ size = sb.st_size;
+
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %lld bytes\n",
+ (long long) size);
+ exit(EXIT_FAILURE);
+ }
+
+ n = fread(buffer, 1, size, fd);
+ if (n != size) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ fclose(fd);
+
+ *data_size = size;
+ return buffer;
+}
+
+static void warning(const char *context, nsgif_error err)
+{
+ fprintf(stderr, "%s failed: %s\n",
+ context, nsgif_strerror(err));
+}
+
+static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
+{
+ nsgif_error err;
+ uint32_t frame_prev = 0;
+ const nsgif_info_t *info;
+
+ info = nsgif_get_info(gif);
+
+ if (write_ppm) {
+ fprintf(fh, "P3\n");
+ fprintf(fh, "# %s\n", name);
+ fprintf(fh, "# width %u \n", info->width);
+ fprintf(fh, "# height %u \n", info->height);
+ fprintf(fh, "# frame_count %u \n", info->frame_count);
+ fprintf(fh, "# loop_max %u \n", info->loop_max);
+ fprintf(fh, "%u %u 256\n", info->width,
+ info->height * info->frame_count);
+ }
+
+ /* decode the frames */
+ while (true) {
+ nsgif_bitmap_t *buffer;
+ const uint8_t *image;
+ uint32_t frame_new;
+ uint32_t delay_cs;
+ nsgif_rect area;
+
+ err = nsgif_frame_prepare(gif, &area,
+ &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ warning("nsgif_frame_prepare", err);
+ return;
+ }
+
+ if (frame_new < frame_prev) {
+ /* Must be an animation that loops. We only care about
+ * decoding each frame once. */
+ return;
+ }
+ frame_prev = frame_new;
+
+ err = nsgif_frame_decode(gif, frame_new, &buffer);
+ if (err != NSGIF_OK) {
+ warning("nsgif_decode_frame", err);
+ return;
+ }
+
+ if (write_ppm) {
+ fprintf(fh, "# frame %u:\n", frame_new);
+ image = (const uint8_t *) buffer;
+ for (uint32_t y = 0; y != info->height; y++) {
+ for (uint32_t x = 0; x != info->width; x++) {
+ size_t z = (y * info->width + x) * 4;
+ fprintf(fh, "%u %u %u ",
+ image[z],
+ image[z + 1],
+ image[z + 2]);
+ }
+ fprintf(fh, "\n");
+ }
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const nsgif_bitmap_cb_vt bitmap_callbacks = {
+ .create = bitmap_create,
+ .destroy = bitmap_destroy,
+ .get_buffer = bitmap_get_buffer,
+ };
+ nsgif *gif;
+ size_t size;
+ uint8_t *data;
+ nsgif_error err;
+ FILE *outf = stdout;
+ bool no_write = false;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
+ "but not output.\n");
+ fprintf(stderr, "Otherwise [out] is an output filename.\n");
+ fprintf(stderr, "When [out] is unset, output is to stdout.\n");
+
+ return 1;
+ }
+
+ if (argc > 2) {
+ if (strcmp(argv[2], "NOWRITE") == 0) {
+ no_write = true;
+ } else {
+ outf = fopen(argv[2], "w+");
+ if (outf == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
+ return 2;
+ }
+ }
+ }
+
+ /* create our gif animation */
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ return 1;
+ }
+
+ /* load file into memory */
+ data = load_file(argv[1], &size);
+
+ /* Scan the raw data */
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ warning("nsgif_data_scan", err);
+ nsgif_destroy(gif);
+ free(data);
+ return 1;
+ }
+
+ decode(outf, argv[1], gif, !no_write);
+
+ if (argc > 2 && !no_write) {
+ fclose(outf);
+ }
+
+ /* clean up */
+ nsgif_destroy(gif);
+ free(data);
+
+ return 0;
+}
diff --git a/test/runtest.sh b/test/runtest.sh
index 05665a1..5c22709 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -23,7 +23,7 @@ gifdecode()
OUTF=$(basename ${1} .gif)
CMPF=$(dirname ${1})/${OUTF}.ppm
echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_decode_gif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
ECODE=$?
echo "Exit code:${ECODE}" >> ${TEST_LOG}
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=d7a746a7af4265825a...
commit d7a746a7af4265825add1d6ecd44d0699c8d238d
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: First frame establishes a redraw rectangle.
diff --git a/src/gif.c b/src/gif.c
index 79ea835..3374b1e 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -1633,7 +1633,6 @@ nsgif_error nsgif_frame_prepare(
uint32_t frame_next;
if (gif->frame != NSGIF_FRAME_INVALID &&
- gif->frame != 0 &&
gif->frame < gif->info.frame_count &&
gif->frames[gif->frame].display) {
rect = gif->frames[gif->frame].redraw;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=f19985bde06da13b2a...
commit f19985bde06da13b2a1396756b2c82300296f8c8
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
API: Clean up error codes and add nsgif_strerror function.
diff --git a/include/nsgif.h b/include/nsgif.h
index 1472842..f03d121 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -39,20 +39,61 @@ typedef struct nsgif_info {
uint16_t delay_min;
} nsgif_info_t;
-/* Error return values */
+/**
+ * NSGIF return codes.
+ */
typedef enum {
- NSGIF_WORKING = 1,
- NSGIF_OK = 0,
- NSGIF_INSUFFICIENT_DATA = -1,
- NSGIF_INSUFFICIENT_FRAME_DATA = NSGIF_INSUFFICIENT_DATA,
- NSGIF_FRAME_DATA_ERROR = -2,
- NSGIF_DATA_ERROR = -4,
- NSGIF_INSUFFICIENT_MEMORY = -5,
- NSGIF_FRAME_NO_DISPLAY = -6,
- NSGIF_END_OF_FRAME = -7,
- NSGIF_FRAME_INVALID = -8,
- NSGIF_ANIMATION_COMPLETE = -9,
-} nsgif_result;
+ /**
+ * Success.
+ */
+ NSGIF_OK,
+
+ /**
+ * Out of memory error.
+ */
+ NSGIF_ERR_OOM,
+
+ /**
+ * GIF source data is invalid, and no frames are recoverable.
+ */
+ NSGIF_ERR_DATA,
+
+ /**
+ * Frame number is not valid.
+ */
+ NSGIF_ERR_BAD_FRAME,
+
+ /**
+ * GIF source data contained an error in a frame.
+ */
+ NSGIF_ERR_DATA_FRAME,
+
+ /**
+ * Too many frames.
+ */
+ NSGIF_ERR_FRAME_COUNT,
+
+ /**
+ * GIF source data ended without one complete frame available.
+ */
+ NSGIF_ERR_END_OF_DATA,
+
+ /**
+ * GIF source data ended with incomplete frame.
+ */
+ NSGIF_ERR_END_OF_FRAME,
+
+ /**
+ * The current frame cannot be displayed.
+ */
+ NSGIF_ERR_FRAME_DISPLAY,
+
+ /**
+ * Indicates an animation is complete, and \ref nsgif_reset must be
+ * called to restart the animation from the beginning.
+ */
+ NSGIF_ERR_ANIMATION_END,
+} nsgif_error;
/**
* GIF rectangle structure.
@@ -136,13 +177,22 @@ typedef struct nsgif_bitmap_cb_vt {
} nsgif_bitmap_cb_vt;
/**
+ * Convert an error code to a string.
+ *
+ * \param[in] err The error code to convert.
+ * \return String representation of given error code.
+ */
+const char *nsgif_strerror(nsgif_error err);
+
+/**
* Create the NSGIF object.
*
* \param[in] bitmap_vt Bitmap operation functions v-table.
* \param[out] gif_out Return NSGIF object on success.
+ *
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
/**
* Scan the source image data.
@@ -166,15 +216,9 @@ nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
* \param[in] size Number of bytes in data.
* \param[in] data Raw source GIF data.
*
- * \return Error return value.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - NSGIF_INSUFFICIENT_MEMORY for memory error
- * - NSGIF_DATA_ERROR for GIF error
- * - NSGIF_OK for successful decoding
- * - NSGIF_WORKING for successful decoding if more frames are expected
+ * \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_data_scan(
+nsgif_error nsgif_data_scan(
nsgif *gif,
size_t size,
const uint8_t *data);
@@ -186,8 +230,10 @@ nsgif_result nsgif_data_scan(
* \param[out] area The area in pixels that must be redrawn.
* \param[out] delay_cs Time to wait after frame_new before next frame in cs.
* \param[out] frame_new The frame to decode.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_frame_prepare(
+nsgif_error nsgif_frame_prepare(
nsgif *gif,
nsgif_rect *area,
uint32_t *delay_cs,
@@ -200,14 +246,10 @@ nsgif_result nsgif_frame_prepare(
* \param[in] frame The frame number to decode.
* \param[out] bitmap On success, returns pointer to the client-allocated,
* nsgif-owned client bitmap structure.
- * \return Error return value.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_DATA_ERROR for GIF error (invalid frame header)
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - NSGIF_OK for successful decoding
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_frame_decode(
+nsgif_error nsgif_frame_decode(
nsgif *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap);
@@ -224,7 +266,7 @@ nsgif_result nsgif_frame_decode(
*
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_reset(
+nsgif_error nsgif_reset(
nsgif *gif);
/**
diff --git a/src/gif.c b/src/gif.c
index 5ed38d8..79ea835 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -104,6 +104,14 @@ struct nsgif {
};
/**
+ * Helper macro to get number of elements in an array.
+ *
+ * \param[in] _a Array to count elements of.
+ * \return NUlber of elements in array.
+ */
+#define NSGIF_ARRAY_LEN(_a) ((sizeof(_a)) / (sizeof(*_a)))
+
+/**
*
* \file
* \brief GIF image decoder
@@ -146,16 +154,16 @@ enum nsgif_disposal {
* \param[in] l_res LZW response code.
* \return GIF result code.
*/
-static nsgif_result nsgif__error_from_lzw(lzw_result l_res)
+static nsgif_error nsgif__error_from_lzw(lzw_result l_res)
{
- static const nsgif_result g_res[] = {
+ static const nsgif_error g_res[] = {
[LZW_OK] = NSGIF_OK,
- [LZW_OK_EOD] = NSGIF_END_OF_FRAME,
- [LZW_NO_MEM] = NSGIF_INSUFFICIENT_MEMORY,
- [LZW_NO_DATA] = NSGIF_INSUFFICIENT_DATA,
- [LZW_EOI_CODE] = NSGIF_FRAME_DATA_ERROR,
- [LZW_BAD_ICODE] = NSGIF_FRAME_DATA_ERROR,
- [LZW_BAD_CODE] = NSGIF_FRAME_DATA_ERROR,
+ [LZW_NO_MEM] = NSGIF_ERR_OOM,
+ [LZW_OK_EOD] = NSGIF_ERR_END_OF_DATA,
+ [LZW_NO_DATA] = NSGIF_ERR_END_OF_DATA,
+ [LZW_EOI_CODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_ICODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_CODE] = NSGIF_ERR_DATA_FRAME,
};
assert(l_res != LZW_BAD_PARAM);
assert(l_res != LZW_NO_COLOUR);
@@ -168,9 +176,9 @@ static nsgif_result nsgif__error_from_lzw(lzw_result l_res)
* \param gif The animation context
* \param width The width of the sprite
* \param height The height of the sprite
- * \return NSGIF_INSUFFICIENT_MEMORY for a memory error NSGIF_OK for success
+ * \return NSGIF_ERR_OOM for a memory error NSGIF_OK for success
*/
-static nsgif_result nsgif__initialise_sprite(
+static nsgif_error nsgif__initialise_sprite(
struct nsgif *gif,
uint32_t width,
uint32_t height)
@@ -183,7 +191,7 @@ static nsgif_result nsgif__initialise_sprite(
assert(gif->bitmap.create);
gif->frame_image = gif->bitmap.create(width, height);
if (gif->frame_image == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
return NSGIF_OK;
@@ -198,7 +206,7 @@ static nsgif_result nsgif__initialise_sprite(
static inline uint32_t* nsgif__bitmap_get(
struct nsgif *gif)
{
- nsgif_result ret;
+ nsgif_error ret;
/* Make sure we have a buffer to decode to. */
ret = nsgif__initialise_sprite(gif, gif->info.width, gif->info.height);
@@ -292,7 +300,7 @@ static void nsgif__record_frame(
gif->prev_index = gif->decoded_frame;
}
-static nsgif_result nsgif__recover_frame(
+static nsgif_error nsgif__recover_frame(
const struct nsgif *gif,
uint32_t *bitmap)
{
@@ -300,10 +308,6 @@ static nsgif_result nsgif__recover_frame(
unsigned height = gif->info.height;
unsigned width = gif->info.width;
- if (prev_frame == NULL) {
- return NSGIF_FRAME_DATA_ERROR;
- }
-
memcpy(bitmap, prev_frame, height * width * sizeof(*bitmap));
return NSGIF_OK;
@@ -403,7 +407,7 @@ static inline void gif__jump_data(
*pos += jump;
}
-static nsgif_result nsgif__decode_complex(
+static nsgif_error nsgif__decode_complex(
struct nsgif *gif,
uint32_t width,
uint32_t height,
@@ -416,7 +420,7 @@ static nsgif_result nsgif__decode_complex(
uint32_t *restrict colour_table)
{
lzw_result res;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
uint32_t clip_x = gif__clip(offset_x, width, gif->info.width);
uint32_t clip_y = gif__clip(offset_y, height, gif->info.height);
const uint8_t *uncompressed;
@@ -502,7 +506,7 @@ static nsgif_result nsgif__decode_complex(
return ret;
}
-static nsgif_result nsgif__decode_simple(
+static nsgif_error nsgif__decode_simple(
struct nsgif *gif,
uint32_t height,
uint32_t offset_y,
@@ -513,7 +517,7 @@ static nsgif_result nsgif__decode_simple(
{
uint32_t pixels = gif->info.width * height;
uint32_t written = 0;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
lzw_result res;
if (offset_y >= gif->info.height) {
@@ -560,7 +564,7 @@ static nsgif_result nsgif__decode_simple(
return ret;
}
-static inline nsgif_result nsgif__decode(
+static inline nsgif_error nsgif__decode(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
@@ -570,7 +574,7 @@ static inline nsgif_result nsgif__decode(
GIF_MASK_INTERLACE = 0x40,
};
- nsgif_result ret;
+ nsgif_error ret;
uint32_t width = frame->redraw.x1 - frame->redraw.x0;
uint32_t height = frame->redraw.y1 - frame->redraw.y0;
uint32_t offset_x = frame->redraw.x0;
@@ -640,20 +644,20 @@ static void nsgif__restore_bg(
}
}
-static nsgif_result nsgif__update_bitmap(
+static nsgif_error nsgif__update_bitmap(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
uint32_t frame_idx)
{
- nsgif_result ret;
+ nsgif_error ret;
uint32_t *bitmap;
gif->decoded_frame = frame_idx;
bitmap = nsgif__bitmap_get(gif);
if (bitmap == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
/* Handle any bitmap clearing/restoration required before decoding this
@@ -700,10 +704,10 @@ static nsgif_result nsgif__update_bitmap(
* \param[in] frame The gif object we're decoding.
* \param[in] data The data to decode.
* \param[in] len Byte length of data.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_extension_graphic_control(
+static nsgif_error nsgif__parse_extension_graphic_control(
const struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
@@ -727,7 +731,7 @@ static nsgif_result nsgif__parse_extension_graphic_control(
* +5 CHAR Transparent Color Index
*/
if (len < 6) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
frame->frame_delay = data[3] | (data[4] << 8);
@@ -766,10 +770,10 @@ static nsgif_result nsgif__parse_extension_graphic_control(
* \param[in] gif The gif object we're decoding.
* \param[in] data The data to decode.
* \param[in] len Byte length of data.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_extension_application(
+static nsgif_error nsgif__parse_extension_application(
struct nsgif *gif,
const uint8_t *data,
size_t len)
@@ -783,7 +787,7 @@ static nsgif_result nsgif__parse_extension_application(
* +13 1-256 Application Data (Data sub-blocks)
*/
if (len < 17) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if ((data[1] == 0x0b) &&
@@ -802,10 +806,10 @@ static nsgif_result nsgif__parse_extension_application(
* \param[in] frame The frame to parse extensions for.
* \param[in] pos Current position in data, updated on exit.
* \param[in] decode Whether to decode or skip over the extension.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_frame_extensions(
+static nsgif_error nsgif__parse_frame_extensions(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
@@ -825,13 +829,13 @@ static nsgif_result nsgif__parse_frame_extensions(
/* Initialise the extensions */
while (nsgif_bytes > 0 && nsgif_data[0] == GIF_EXT_INTRODUCER) {
bool block_step = true;
- nsgif_result ret;
+ nsgif_error ret;
nsgif_data++;
nsgif_bytes--;
if (nsgif_bytes == 0) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
/* Switch on extension label */
@@ -875,7 +879,7 @@ static nsgif_result nsgif__parse_frame_extensions(
* the extension size itself
*/
if (nsgif_bytes < 2) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
nsgif_data += 2 + nsgif_data[1];
}
@@ -885,7 +889,7 @@ static nsgif_result nsgif__parse_frame_extensions(
while (nsgif_data < nsgif_end && nsgif_data[0] != NSGIF_BLOCK_TERMINATOR) {
nsgif_data += nsgif_data[0] + 1;
if (nsgif_data >= nsgif_end) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
}
nsgif_data++;
@@ -924,7 +928,7 @@ static nsgif_result nsgif__parse_frame_extensions(
* \param[in] decode Whether to decode the image descriptor.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_image_descriptor(
+static nsgif_error nsgif__parse_image_descriptor(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
@@ -941,14 +945,14 @@ static nsgif_result nsgif__parse_image_descriptor(
assert(frame != NULL);
if (len < NSGIF_IMAGE_DESCRIPTOR_LEN) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (decode) {
uint32_t x, y, w, h;
if (data[0] != NSGIF_IMAGE_SEPARATOR) {
- return NSGIF_FRAME_DATA_ERROR;
+ return NSGIF_ERR_DATA_FRAME;
}
x = data[1] | (data[2] << 8);
@@ -987,7 +991,7 @@ static nsgif_result nsgif__parse_image_descriptor(
* \param[in] decode Whether to decode the colour table.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__colour_table_extract(
+static nsgif_error nsgif__colour_table_extract(
struct nsgif *gif,
uint32_t colour_table[NSGIF_MAX_COLOURS],
size_t colour_table_entries,
@@ -998,7 +1002,7 @@ static nsgif_result nsgif__colour_table_extract(
size_t len = gif->nsgif_data + gif->buffer_size - data;
if (len < colour_table_entries * 3) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (decode) {
@@ -1036,13 +1040,13 @@ static nsgif_result nsgif__colour_table_extract(
* \param[in] decode Whether to decode the colour table.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_colour_table(
+static nsgif_error nsgif__parse_colour_table(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
bool decode)
{
- nsgif_result ret;
+ nsgif_error ret;
assert(gif != NULL);
assert(frame != NULL);
@@ -1074,7 +1078,7 @@ static nsgif_result nsgif__parse_colour_table(
* \param[in] decode Whether to decode the image data.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_image_data(
+static nsgif_error nsgif__parse_image_data(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
@@ -1084,7 +1088,7 @@ static nsgif_result nsgif__parse_image_data(
size_t len = gif->nsgif_data + gif->buffer_size - data;
uint32_t frame_idx = frame - gif->frames;
uint8_t minimum_code_size;
- nsgif_result ret;
+ nsgif_error ret;
assert(gif != NULL);
assert(frame != NULL);
@@ -1103,12 +1107,12 @@ static nsgif_result nsgif__parse_image_data(
/* Fall through. */
case 1: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
/* Fall through. */
- case 0: return NSGIF_INSUFFICIENT_DATA;
+ case 0: return NSGIF_ERR_END_OF_DATA;
}
minimum_code_size = data[0];
if (minimum_code_size >= LZW_CODE_MAX) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA_FRAME;
}
if (decode) {
@@ -1121,7 +1125,7 @@ static nsgif_result nsgif__parse_image_data(
len--;
while (block_size != 1) {
- if (len < 1) return NSGIF_INSUFFICIENT_DATA;
+ if (len < 1) return NSGIF_ERR_END_OF_DATA;
block_size = data[0] + 1;
/* Check if the frame data runs off the end of the file */
if (block_size > len) {
@@ -1133,20 +1137,12 @@ static nsgif_result nsgif__parse_image_data(
data += block_size;
}
- gif->info.frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
*pos = data;
- /* Check if we've finished */
- if (len < 1) {
- return NSGIF_INSUFFICIENT_DATA;
- } else {
- if (data[0] == NSGIF_TRAILER) {
- return NSGIF_OK;
- }
- }
+ gif->info.frame_count = frame_idx + 1;
+ gif->frames[frame_idx].display = true;
- return NSGIF_WORKING;
+ return NSGIF_OK;
}
return ret;
@@ -1193,27 +1189,21 @@ static struct nsgif_frame *nsgif__get_frame(
* \param[in] gif The animation context
* \param[in] frame_idx The frame number to decode.
* \param[in] decode Whether to decode the graphical image data.
- * \return error code
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - NSGIF_DATA_ERROR for GIF error (invalid frame header)
- * - NSGIF_OK for successful decoding
- * - NSGIF_WORKING for successful decoding if more frames are expected
+ * \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__process_frame(
+static nsgif_error nsgif__process_frame(
struct nsgif *gif,
uint32_t frame_idx,
bool decode)
{
- nsgif_result ret;
+ nsgif_error ret;
const uint8_t *pos;
const uint8_t *end;
struct nsgif_frame *frame;
frame = nsgif__get_frame(gif, frame_idx);
if (frame == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
end = gif->nsgif_data + gif->buffer_size;
@@ -1228,7 +1218,7 @@ static nsgif_result nsgif__process_frame(
/* Ensure the frame is in range to decode */
if (frame_idx > gif->frame_count_partial) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
/* Done if frame is already decoded */
@@ -1247,7 +1237,7 @@ static nsgif_result nsgif__process_frame(
* millions of frames, so we ensure that we don't have a
* silly number. */
if (frame_idx > 4096) {
- return NSGIF_FRAME_DATA_ERROR;
+ return NSGIF_ERR_FRAME_COUNT;
}
}
@@ -1306,13 +1296,13 @@ void nsgif_destroy(nsgif *gif)
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
{
nsgif *gif;
gif = calloc(1, sizeof(*gif));
if (gif == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
gif->bitmap = *bitmap_vt;
@@ -1339,7 +1329,7 @@ nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
* \param[in] strict Whether to require a known GIF version.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_header(
+static nsgif_error nsgif__parse_header(
struct nsgif *gif,
const uint8_t **pos,
bool strict)
@@ -1348,18 +1338,18 @@ static nsgif_result nsgif__parse_header(
size_t len = gif->nsgif_data + gif->buffer_size - data;
if (len < 6) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (strncmp((const char *) data, "GIF", 3) != 0) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA;
}
data += 3;
if (strict == true) {
if ((strncmp((const char *) data, "87a", 3) != 0) &&
(strncmp((const char *) data, "89a", 3) != 0)) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA;
}
}
data += 3;
@@ -1387,7 +1377,7 @@ static nsgif_result nsgif__parse_header(
* \param[in,out] pos The current buffer position, updated on success.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_logical_screen_descriptor(
+static nsgif_error nsgif__parse_logical_screen_descriptor(
struct nsgif *gif,
const uint8_t **pos)
{
@@ -1395,7 +1385,7 @@ static nsgif_result nsgif__parse_logical_screen_descriptor(
size_t len = gif->nsgif_data + gif->buffer_size - data;
if (len < 7) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
gif->info.width = data[0] | (data[1] << 8);
@@ -1411,13 +1401,14 @@ static nsgif_result nsgif__parse_logical_screen_descriptor(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_data_scan(
+nsgif_error nsgif_data_scan(
nsgif *gif,
size_t size,
const uint8_t *data)
{
const uint8_t *nsgif_data;
- nsgif_result ret;
+ nsgif_error ret;
+ uint32_t frames;
/* Initialize values */
gif->buffer_size = size;
@@ -1533,10 +1524,16 @@ nsgif_result nsgif_data_scan(
}
}
- /* Repeatedly try to initialise frames */
+ /* Try to initialise all frames. */
do {
- ret = nsgif__process_frame(gif, gif->info.frame_count, false);
- } while (ret == NSGIF_WORKING);
+ frames = gif->info.frame_count;
+ ret = nsgif__process_frame(gif, frames, false);
+ } while (gif->info.frame_count > frames);
+
+ if (ret == NSGIF_ERR_END_OF_DATA &&
+ gif->info.frame_count > 0) {
+ ret = NSGIF_ERR_END_OF_FRAME;
+ }
return ret;
}
@@ -1578,7 +1575,7 @@ static uint32_t nsgif__frame_next(
return (frame >= frames) ? 0 : frame;
}
-static nsgif_result nsgif__next_displayable_frame(
+static nsgif_error nsgif__next_displayable_frame(
nsgif *gif,
uint32_t *frame,
uint32_t *delay)
@@ -1588,7 +1585,7 @@ static nsgif_result nsgif__next_displayable_frame(
do {
next = nsgif__frame_next(gif, false, next);
if (next == *frame || next == NSGIF_FRAME_INVALID) {
- return NSGIF_FRAME_NO_DISPLAY;
+ return NSGIF_ERR_FRAME_DISPLAY;
}
if (delay != NULL) {
@@ -1610,7 +1607,7 @@ static inline bool nsgif__animation_complete(int count, int max)
return (count >= max);
}
-nsgif_result nsgif_reset(
+nsgif_error nsgif_reset(
nsgif *gif)
{
gif->info.loop_count = 0;
@@ -1620,13 +1617,13 @@ nsgif_result nsgif_reset(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_frame_prepare(
+nsgif_error nsgif_frame_prepare(
nsgif *gif,
nsgif_rect *area,
uint32_t *delay_cs,
uint32_t *frame_new)
{
- nsgif_result ret;
+ nsgif_error ret;
nsgif_rect rect = {
.x1 = 0,
.y1 = 0,
@@ -1645,7 +1642,7 @@ nsgif_result nsgif_frame_prepare(
if (nsgif__animation_complete(
gif->info.loop_count,
gif->info.loop_max)) {
- return NSGIF_ANIMATION_COMPLETE;
+ return NSGIF_ERR_ANIMATION_END;
}
ret = nsgif__next_displayable_frame(gif, &frame, NULL);
@@ -1682,13 +1679,17 @@ nsgif_result nsgif_frame_prepare(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_frame_decode(
+nsgif_error nsgif_frame_decode(
nsgif *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap)
{
uint32_t start_frame;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
+
+ if (frame > gif->info.frame_count) {
+ return NSGIF_ERR_BAD_FRAME;
+ }
if (gif->decoded_frame == frame) {
*bitmap = gif->frame_image;
@@ -1718,3 +1719,26 @@ const nsgif_info_t *nsgif_get_info(const nsgif *gif)
{
return &gif->info;
}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_strerror(nsgif_error err)
+{
+ static const char *const str[] = {
+ [NSGIF_OK] = "Success",
+ [NSGIF_ERR_OOM] = "Out of memory",
+ [NSGIF_ERR_DATA] = "Invalid source data",
+ [NSGIF_ERR_BAD_FRAME] = "Requested frame does not exist",
+ [NSGIF_ERR_DATA_FRAME] = "Invalid frame data",
+ [NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames",
+ [NSGIF_ERR_END_OF_DATA] = "Insufficient data for first frame",
+ [NSGIF_ERR_END_OF_FRAME] = "End of data during frame",
+ [NSGIF_ERR_FRAME_DISPLAY] = "Frame can't be displayed",
+ [NSGIF_ERR_ANIMATION_END] = "Animation complete",
+ };
+
+ if (err >= NSGIF_ARRAY_LEN(str) || str[err] == NULL) {
+ return "Unknown error";
+ }
+
+ return str[err];
+}
diff --git a/test/decode_gif.c b/test/decode_gif.c
index acc2ef0..173f70c 100644
--- a/test/decode_gif.c
+++ b/test/decode_gif.c
@@ -78,32 +78,15 @@ static uint8_t *load_file(const char *path, size_t *data_size)
return buffer;
}
-static void warning(const char *context, nsgif_result gif_res)
+static void warning(const char *context, nsgif_error err)
{
- fprintf(stderr, "%s failed: ", context);
- switch (gif_res) {
- case NSGIF_FRAME_DATA_ERROR:
- fprintf(stderr, "NSGIF_FRAME_DATA_ERROR");
- break;
- case NSGIF_INSUFFICIENT_DATA:
- fprintf(stderr, "NSGIF_INSUFFICIENT_DATA");
- break;
- case NSGIF_DATA_ERROR:
- fprintf(stderr, "NSGIF_DATA_ERROR");
- break;
- case NSGIF_INSUFFICIENT_MEMORY:
- fprintf(stderr, "NSGIF_INSUFFICIENT_MEMORY");
- break;
- default:
- fprintf(stderr, "unknown code %i", gif_res);
- break;
- }
- fprintf(stderr, "\n");
+ fprintf(stderr, "%s failed: %s\n",
+ context, nsgif_strerror(err));
}
static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
{
- nsgif_result gif_res;
+ nsgif_error err;
uint32_t frame_prev = 0;
const nsgif_info_t *info;
@@ -128,10 +111,10 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
uint32_t delay_cs;
nsgif_rect area;
- gif_res = nsgif_frame_prepare(gif, &area,
+ err = nsgif_frame_prepare(gif, &area,
&delay_cs, &frame_new);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_frame_prepare", gif_res);
+ if (err != NSGIF_OK) {
+ warning("nsgif_frame_prepare", err);
return;
}
@@ -142,9 +125,9 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
}
frame_prev = frame_new;
- gif_res = nsgif_frame_decode(gif, frame_new, &buffer);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_decode_frame", gif_res);
+ err = nsgif_frame_decode(gif, frame_new, &buffer);
+ if (err != NSGIF_OK) {
+ warning("nsgif_decode_frame", err);
return;
}
@@ -175,8 +158,8 @@ int main(int argc, char *argv[])
nsgif *gif;
size_t size;
uint8_t *data;
+ nsgif_error err;
FILE *outf = stdout;
- nsgif_result gif_res;
bool no_write = false;
if (argc < 2) {
@@ -203,8 +186,8 @@ int main(int argc, char *argv[])
}
/* create our gif animation */
- gif_res = nsgif_create(&bitmap_callbacks, &gif);
- if (gif_res != NSGIF_OK) {
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
return 1;
}
@@ -212,9 +195,9 @@ int main(int argc, char *argv[])
data = load_file(argv[1], &size);
/* Scan the raw data */
- gif_res = nsgif_data_scan(gif, size, data);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_data_scan", gif_res);
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ warning("nsgif_data_scan", err);
nsgif_destroy(gif);
free(data);
return 1;
-----------------------------------------------------------------------
Summary of changes:
include/nsgif.h | 220 ++++++++++-----
src/gif.c | 442 +++++++++++++++++--------------
test/Makefile | 2 +-
test/cli.c | 763 +++++++++++++++++++++++++++++++++++++++++++++++++++++
test/cli.h | 94 +++++++
test/decode_gif.c | 234 ----------------
test/nsgif.c | 299 +++++++++++++++++++++
test/runtest.sh | 62 ++---
8 files changed, 1592 insertions(+), 524 deletions(-)
create mode 100644 test/cli.c
create mode 100644 test/cli.h
delete mode 100644 test/decode_gif.c
create mode 100644 test/nsgif.c
diff --git a/include/nsgif.h b/include/nsgif.h
index 1472842..2c8a60c 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -20,39 +20,10 @@
#include <stdbool.h>
#include <inttypes.h>
+/** Representation of infinity. */
#define NSGIF_INFINITE (UINT32_MAX)
-typedef struct nsgif nsgif;
-
-typedef struct nsgif_info {
- /** width of GIF (may increase during decoding) */
- uint32_t width;
- /** height of GIF (may increase during decoding) */
- uint32_t height;
- /** number of frames decoded */
- uint32_t frame_count;
- /** number of times to loop animation */
- int loop_max;
- /** number of animation loops so far */
- int loop_count;
-
- uint16_t delay_min;
-} nsgif_info_t;
-
-/* Error return values */
-typedef enum {
- NSGIF_WORKING = 1,
- NSGIF_OK = 0,
- NSGIF_INSUFFICIENT_DATA = -1,
- NSGIF_INSUFFICIENT_FRAME_DATA = NSGIF_INSUFFICIENT_DATA,
- NSGIF_FRAME_DATA_ERROR = -2,
- NSGIF_DATA_ERROR = -4,
- NSGIF_INSUFFICIENT_MEMORY = -5,
- NSGIF_FRAME_NO_DISPLAY = -6,
- NSGIF_END_OF_FRAME = -7,
- NSGIF_FRAME_INVALID = -8,
- NSGIF_ANIMATION_COMPLETE = -9,
-} nsgif_result;
+typedef struct nsgif nsgif_t;
/**
* GIF rectangle structure.
@@ -71,7 +42,63 @@ typedef struct nsgif_rect {
uint32_t x1;
/** y co-ordinate of redraw rectangle, bottom */
uint32_t y1;
-} nsgif_rect;
+} nsgif_rect_t;
+
+/**
+ * NSGIF return codes.
+ */
+typedef enum {
+ /**
+ * Success.
+ */
+ NSGIF_OK,
+
+ /**
+ * Out of memory error.
+ */
+ NSGIF_ERR_OOM,
+
+ /**
+ * GIF source data is invalid, and no frames are recoverable.
+ */
+ NSGIF_ERR_DATA,
+
+ /**
+ * Frame number is not valid.
+ */
+ NSGIF_ERR_BAD_FRAME,
+
+ /**
+ * GIF source data contained an error in a frame.
+ */
+ NSGIF_ERR_DATA_FRAME,
+
+ /**
+ * Too many frames.
+ */
+ NSGIF_ERR_FRAME_COUNT,
+
+ /**
+ * GIF source data ended without one complete frame available.
+ */
+ NSGIF_ERR_END_OF_DATA,
+
+ /**
+ * GIF source data ended with incomplete frame.
+ */
+ NSGIF_ERR_END_OF_FRAME,
+
+ /**
+ * The current frame cannot be displayed.
+ */
+ NSGIF_ERR_FRAME_DISPLAY,
+
+ /**
+ * Indicates an animation is complete, and \ref nsgif_reset must be
+ * called to restart the animation from the beginning.
+ */
+ NSGIF_ERR_ANIMATION_END,
+} nsgif_error;
/**
* Client bitmap type.
@@ -136,13 +163,31 @@ typedef struct nsgif_bitmap_cb_vt {
} nsgif_bitmap_cb_vt;
/**
+ * Convert an error code to a string.
+ *
+ * \param[in] err The error code to convert.
+ * \return String representation of given error code.
+ */
+const char *nsgif_strerror(nsgif_error err);
+
+/**
* Create the NSGIF object.
*
* \param[in] bitmap_vt Bitmap operation functions v-table.
* \param[out] gif_out Return NSGIF object on success.
+ *
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
+nsgif_error nsgif_create(
+ const nsgif_bitmap_cb_vt *bitmap_vt,
+ nsgif_t **gif_out);
+
+/**
+ * Free a NSGIF object.
+ *
+ * \param[in] gif The NSGIF to free.
+ */
+void nsgif_destroy(nsgif_t *gif);
/**
* Scan the source image data.
@@ -166,30 +211,30 @@ nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out);
* \param[in] size Number of bytes in data.
* \param[in] data Raw source GIF data.
*
- * \return Error return value.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - NSGIF_INSUFFICIENT_MEMORY for memory error
- * - NSGIF_DATA_ERROR for GIF error
- * - NSGIF_OK for successful decoding
- * - NSGIF_WORKING for successful decoding if more frames are expected
+ * \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_data_scan(
- nsgif *gif,
+nsgif_error nsgif_data_scan(
+ nsgif_t *gif,
size_t size,
const uint8_t *data);
/**
* Prepare to show a frame.
*
+ * If this is the last frame of an animation with a finite loop count, the
+ * returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
+ * should be shown forever.
+ *
* \param[in] gif The NSGIF object.
* \param[out] area The area in pixels that must be redrawn.
* \param[out] delay_cs Time to wait after frame_new before next frame in cs.
* \param[out] frame_new The frame to decode.
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+nsgif_error nsgif_frame_prepare(
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new);
@@ -200,15 +245,11 @@ nsgif_result nsgif_frame_prepare(
* \param[in] frame The frame number to decode.
* \param[out] bitmap On success, returns pointer to the client-allocated,
* nsgif-owned client bitmap structure.
- * \return Error return value.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_DATA_ERROR for GIF error (invalid frame header)
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data
- * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - NSGIF_OK for successful decoding
- */
-nsgif_result nsgif_frame_decode(
- nsgif *gif,
+ *
+ * \return NSGIF_OK on success, or appropriate error otherwise.
+ */
+nsgif_error nsgif_frame_decode(
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap);
@@ -224,8 +265,62 @@ nsgif_result nsgif_frame_decode(
*
* \return NSGIF_OK on success, or appropriate error otherwise.
*/
-nsgif_result nsgif_reset(
- nsgif *gif);
+nsgif_error nsgif_reset(
+ nsgif_t *gif);
+
+/**
+ * Information about a GIF.
+ */
+typedef struct nsgif_info {
+ /** width of GIF (may increase during decoding) */
+ uint32_t width;
+ /** height of GIF (may increase during decoding) */
+ uint32_t height;
+ /** number of frames decoded */
+ uint32_t frame_count;
+ /** number of times to loop animation */
+ int loop_max;
+ /** number of animation loops so far */
+ int loop_count;
+} nsgif_info_t;
+
+/**
+ * Frame disposal method.
+ *
+ * Clients do not need to know about this, it is provided purely for dumping
+ * raw information about GIF frames.
+ */
+enum nsgif_disposal {
+ NSGIF_DISPOSAL_UNSPECIFIED, /**< No disposal method specified. */
+ NSGIF_DISPOSAL_NONE, /**< Frame remains. */
+ NSGIF_DISPOSAL_RESTORE_BG, /**< Clear frame to background colour. */
+ NSGIF_DISPOSAL_RESTORE_PREV, /**< Restore previous frame. */
+ NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
+};
+
+/**
+ * Convert a disposal method to a string.
+ *
+ * \param[in] disposal The disposal method to convert.
+ * \return String representation of given disposal method.
+ */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal);
+
+/**
+ * Information about a GIF frame.
+ */
+typedef struct nsgif_frame_info {
+ /** whether the frame should be displayed/animated */
+ bool display;
+
+ /** Disposal method for previous frame; affects plotting */
+ uint8_t disposal;
+ /** delay (in cs) before animating the frame */
+ uint32_t delay;
+
+ /** Frame's redraw rectangle. */
+ nsgif_rect_t rect;
+} nsgif_frame_info_t;
/**
* Get information about a GIF from an NSGIF object.
@@ -234,13 +329,18 @@ nsgif_result nsgif_reset(
*
* \return The gif info, or NULL on error.
*/
-const nsgif_info_t *nsgif_get_info(const nsgif *gif);
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
/**
- * Free a NSGIF object.
+ * Get information about a GIF from an NSGIF object.
*
- * \param[in] gif The NSGIF to free.
+ * \param[in] gif The NSGIF object to get frame info for.
+ * \param[in] frame The frame number to get info for.
+ *
+ * \return The gif frame info, or NULL on error.
*/
-void nsgif_destroy(nsgif *gif);
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif_t *gif,
+ uint32_t frame);
#endif
diff --git a/src/gif.c b/src/gif.c
index 5ed38d8..5317a9e 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -22,12 +22,7 @@
/** GIF frame data */
typedef struct nsgif_frame {
- /** whether the frame should be displayed/animated */
- bool display;
- /** delay (in cs) before animating the frame */
- uint32_t frame_delay;
-
- /* Internal members are listed below */
+ struct nsgif_frame_info info;
/** offset (in bytes) to the GIF frame data */
uint32_t frame_pointer;
@@ -37,17 +32,14 @@ typedef struct nsgif_frame {
bool opaque;
/** whether a full image redraw is required */
bool redraw_required;
- /** how the previous frame should be disposed; affects plotting */
- uint8_t disposal_method;
+
/** whether we acknowledge transparency */
bool transparency;
/** the index designating a transparent pixel */
uint32_t transparency_index;
+
/* Frame flags */
uint32_t flags;
-
- /** Frame's redraw rectangle. */
- nsgif_rect redraw;
} nsgif_frame;
/** GIF animation data */
@@ -58,8 +50,6 @@ struct nsgif {
void *lzw_ctx;
/** callbacks for bitmap functions */
nsgif_bitmap_cb_vt bitmap;
- /** pointer to GIF data */
- const uint8_t *nsgif_data;
/** decoded frames */
nsgif_frame *frames;
/** current frame */
@@ -69,14 +59,19 @@ struct nsgif {
/** currently decoded image; stored as bitmap from bitmap_create callback */
nsgif_bitmap_t *frame_image;
+ uint16_t delay_min;
uint16_t delay_default;
+
/** number of frames partially decoded */
uint32_t frame_count_partial;
+ /** pointer to GIF data */
+ const uint8_t *buf;
/** current index into GIF data */
- uint32_t buffer_position;
+ uint32_t buf_pos;
/** total number of bytes of GIF data available */
- uint32_t buffer_size;
+ uint32_t buf_len;
+
/** current number of frame holders */
uint32_t frame_holders;
/** background index */
@@ -104,6 +99,14 @@ struct nsgif {
};
/**
+ * Helper macro to get number of elements in an array.
+ *
+ * \param[in] _a Array to count elements of.
+ * \return NUlber of elements in array.
+ */
+#define NSGIF_ARRAY_LEN(_a) ((sizeof(_a)) / (sizeof(*_a)))
+
+/**
*
* \file
* \brief GIF image decoder
@@ -126,14 +129,6 @@ struct nsgif {
/** No transparency */
#define NSGIF_NO_TRANSPARENCY (0xFFFFFFFFu)
-enum nsgif_disposal {
- NSGIF_DISPOSAL_UNSPECIFIED,
- NSGIF_DISPOSAL_NONE,
- NSGIF_DISPOSAL_RESTORE_BG,
- NSGIF_DISPOSAL_RESTORE_PREV,
- NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
-};
-
/* GIF Flags */
#define NSGIF_COLOUR_TABLE_MASK 0x80
#define NSGIF_COLOUR_TABLE_SIZE_MASK 0x07
@@ -146,16 +141,16 @@ enum nsgif_disposal {
* \param[in] l_res LZW response code.
* \return GIF result code.
*/
-static nsgif_result nsgif__error_from_lzw(lzw_result l_res)
+static nsgif_error nsgif__error_from_lzw(lzw_result l_res)
{
- static const nsgif_result g_res[] = {
+ static const nsgif_error g_res[] = {
[LZW_OK] = NSGIF_OK,
- [LZW_OK_EOD] = NSGIF_END_OF_FRAME,
- [LZW_NO_MEM] = NSGIF_INSUFFICIENT_MEMORY,
- [LZW_NO_DATA] = NSGIF_INSUFFICIENT_DATA,
- [LZW_EOI_CODE] = NSGIF_FRAME_DATA_ERROR,
- [LZW_BAD_ICODE] = NSGIF_FRAME_DATA_ERROR,
- [LZW_BAD_CODE] = NSGIF_FRAME_DATA_ERROR,
+ [LZW_NO_MEM] = NSGIF_ERR_OOM,
+ [LZW_OK_EOD] = NSGIF_ERR_END_OF_DATA,
+ [LZW_NO_DATA] = NSGIF_ERR_END_OF_DATA,
+ [LZW_EOI_CODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_ICODE] = NSGIF_ERR_DATA_FRAME,
+ [LZW_BAD_CODE] = NSGIF_ERR_DATA_FRAME,
};
assert(l_res != LZW_BAD_PARAM);
assert(l_res != LZW_NO_COLOUR);
@@ -168,9 +163,9 @@ static nsgif_result nsgif__error_from_lzw(lzw_result l_res)
* \param gif The animation context
* \param width The width of the sprite
* \param height The height of the sprite
- * \return NSGIF_INSUFFICIENT_MEMORY for a memory error NSGIF_OK for success
+ * \return NSGIF_ERR_OOM for a memory error NSGIF_OK for success
*/
-static nsgif_result nsgif__initialise_sprite(
+static nsgif_error nsgif__initialise_sprite(
struct nsgif *gif,
uint32_t width,
uint32_t height)
@@ -183,7 +178,7 @@ static nsgif_result nsgif__initialise_sprite(
assert(gif->bitmap.create);
gif->frame_image = gif->bitmap.create(width, height);
if (gif->frame_image == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
return NSGIF_OK;
@@ -198,7 +193,7 @@ static nsgif_result nsgif__initialise_sprite(
static inline uint32_t* nsgif__bitmap_get(
struct nsgif *gif)
{
- nsgif_result ret;
+ nsgif_error ret;
/* Make sure we have a buffer to decode to. */
ret = nsgif__initialise_sprite(gif, gif->info.width, gif->info.height);
@@ -292,7 +287,7 @@ static void nsgif__record_frame(
gif->prev_index = gif->decoded_frame;
}
-static nsgif_result nsgif__recover_frame(
+static nsgif_error nsgif__recover_frame(
const struct nsgif *gif,
uint32_t *bitmap)
{
@@ -300,10 +295,6 @@ static nsgif_result nsgif__recover_frame(
unsigned height = gif->info.height;
unsigned width = gif->info.width;
- if (prev_frame == NULL) {
- return NSGIF_FRAME_DATA_ERROR;
- }
-
memcpy(bitmap, prev_frame, height * width * sizeof(*bitmap));
return NSGIF_OK;
@@ -403,7 +394,7 @@ static inline void gif__jump_data(
*pos += jump;
}
-static nsgif_result nsgif__decode_complex(
+static nsgif_error nsgif__decode_complex(
struct nsgif *gif,
uint32_t width,
uint32_t height,
@@ -416,7 +407,7 @@ static nsgif_result nsgif__decode_complex(
uint32_t *restrict colour_table)
{
lzw_result res;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
uint32_t clip_x = gif__clip(offset_x, width, gif->info.width);
uint32_t clip_y = gif__clip(offset_y, height, gif->info.height);
const uint8_t *uncompressed;
@@ -439,8 +430,8 @@ static nsgif_result nsgif__decode_complex(
/* Initialise the LZW decoding */
res = lzw_decode_init(gif->lzw_ctx, data[0],
- gif->nsgif_data, gif->buffer_size,
- data + 1 - gif->nsgif_data);
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
if (res != LZW_OK) {
return nsgif__error_from_lzw(res);
}
@@ -502,7 +493,7 @@ static nsgif_result nsgif__decode_complex(
return ret;
}
-static nsgif_result nsgif__decode_simple(
+static nsgif_error nsgif__decode_simple(
struct nsgif *gif,
uint32_t height,
uint32_t offset_y,
@@ -513,7 +504,7 @@ static nsgif_result nsgif__decode_simple(
{
uint32_t pixels = gif->info.width * height;
uint32_t written = 0;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
lzw_result res;
if (offset_y >= gif->info.height) {
@@ -529,8 +520,8 @@ static nsgif_result nsgif__decode_simple(
/* Initialise the LZW decoding */
res = lzw_decode_init_map(gif->lzw_ctx, data[0],
transparency_index, colour_table,
- gif->nsgif_data, gif->buffer_size,
- data + 1 - gif->nsgif_data);
+ gif->buf, gif->buf_len,
+ data + 1 - gif->buf);
if (res != LZW_OK) {
return nsgif__error_from_lzw(res);
}
@@ -560,7 +551,7 @@ static nsgif_result nsgif__decode_simple(
return ret;
}
-static inline nsgif_result nsgif__decode(
+static inline nsgif_error nsgif__decode(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
@@ -570,11 +561,11 @@ static inline nsgif_result nsgif__decode(
GIF_MASK_INTERLACE = 0x40,
};
- nsgif_result ret;
- uint32_t width = frame->redraw.x1 - frame->redraw.x0;
- uint32_t height = frame->redraw.y1 - frame->redraw.y0;
- uint32_t offset_x = frame->redraw.x0;
- uint32_t offset_y = frame->redraw.y0;
+ nsgif_error ret;
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
uint32_t interlace = frame->flags & GIF_MASK_INTERLACE;
uint32_t transparency_index = frame->transparency_index;
uint32_t *restrict colour_table = gif->colour_table;
@@ -609,15 +600,15 @@ static void nsgif__restore_bg(
memset(bitmap, NSGIF_TRANSPARENT_COLOUR,
gif->info.width * gif->info.height * sizeof(*bitmap));
} else {
- uint32_t width = frame->redraw.x1 - frame->redraw.x0;
- uint32_t height = frame->redraw.y1 - frame->redraw.y0;
- uint32_t offset_x = frame->redraw.x0;
- uint32_t offset_y = frame->redraw.y0;
+ uint32_t width = frame->info.rect.x1 - frame->info.rect.x0;
+ uint32_t height = frame->info.rect.y1 - frame->info.rect.y0;
+ uint32_t offset_x = frame->info.rect.x0;
+ uint32_t offset_y = frame->info.rect.y0;
width -= gif__clip(offset_x, width, gif->info.width);
height -= gif__clip(offset_y, height, gif->info.height);
- if (frame->display == false || width == 0) {
+ if (frame->info.display == false || width == 0) {
return;
}
@@ -640,20 +631,20 @@ static void nsgif__restore_bg(
}
}
-static nsgif_result nsgif__update_bitmap(
+static nsgif_error nsgif__update_bitmap(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
uint32_t frame_idx)
{
- nsgif_result ret;
+ nsgif_error ret;
uint32_t *bitmap;
gif->decoded_frame = frame_idx;
bitmap = nsgif__bitmap_get(gif);
if (bitmap == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
/* Handle any bitmap clearing/restoration required before decoding this
@@ -664,10 +655,10 @@ static nsgif_result nsgif__update_bitmap(
} else {
struct nsgif_frame *prev = &gif->frames[frame_idx - 1];
- if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_BG) {
+ if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_BG) {
nsgif__restore_bg(gif, prev, bitmap);
- } else if (prev->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) {
+ } else if (prev->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
ret = nsgif__recover_frame(gif, bitmap);
if (ret != NSGIF_OK) {
nsgif__restore_bg(gif, prev, bitmap);
@@ -675,7 +666,7 @@ static nsgif_result nsgif__update_bitmap(
}
}
- if (frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV) {
+ if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV) {
/* Store the previous frame for later restoration */
nsgif__record_frame(gif, bitmap);
}
@@ -700,10 +691,10 @@ static nsgif_result nsgif__update_bitmap(
* \param[in] frame The gif object we're decoding.
* \param[in] data The data to decode.
* \param[in] len Byte length of data.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_extension_graphic_control(
+static nsgif_error nsgif__parse_extension_graphic_control(
const struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t *data,
@@ -727,12 +718,12 @@ static nsgif_result nsgif__parse_extension_graphic_control(
* +5 CHAR Transparent Color Index
*/
if (len < 6) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
- frame->frame_delay = data[3] | (data[4] << 8);
- if (frame->frame_delay < gif->info.delay_min) {
- frame->frame_delay = gif->delay_default;
+ frame->info.delay = data[3] | (data[4] << 8);
+ if (frame->info.delay < gif->delay_min) {
+ frame->info.delay = gif->delay_default;
}
if (data[2] & GIF_MASK_TRANSPARENCY) {
@@ -740,22 +731,22 @@ static nsgif_result nsgif__parse_extension_graphic_control(
frame->transparency_index = data[5];
}
- frame->disposal_method = ((data[2] & GIF_MASK_DISPOSAL) >> 2);
+ frame->info.disposal = ((data[2] & GIF_MASK_DISPOSAL) >> 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 == NSGIF_DISPOSAL_RESTORE_QUIRK) {
- frame->disposal_method = NSGIF_DISPOSAL_RESTORE_PREV;
+ if (frame->info.disposal == NSGIF_DISPOSAL_RESTORE_QUIRK) {
+ frame->info.disposal = NSGIF_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 == NSGIF_DISPOSAL_RESTORE_BG ||
- frame->disposal_method == NSGIF_DISPOSAL_RESTORE_PREV;
+ frame->info.disposal == NSGIF_DISPOSAL_RESTORE_BG ||
+ frame->info.disposal == NSGIF_DISPOSAL_RESTORE_PREV;
return NSGIF_OK;
}
@@ -766,10 +757,10 @@ static nsgif_result nsgif__parse_extension_graphic_control(
* \param[in] gif The gif object we're decoding.
* \param[in] data The data to decode.
* \param[in] len Byte length of data.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_extension_application(
+static nsgif_error nsgif__parse_extension_application(
struct nsgif *gif,
const uint8_t *data,
size_t len)
@@ -783,7 +774,7 @@ static nsgif_result nsgif__parse_extension_application(
* +13 1-256 Application Data (Data sub-blocks)
*/
if (len < 17) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if ((data[1] == 0x0b) &&
@@ -802,10 +793,10 @@ static nsgif_result nsgif__parse_extension_application(
* \param[in] frame The frame to parse extensions for.
* \param[in] pos Current position in data, updated on exit.
* \param[in] decode Whether to decode or skip over the extension.
- * \return NSGIF_INSUFFICIENT_DATA if more data is needed,
+ * \return NSGIF_ERR_END_OF_DATA if more data is needed,
* NSGIF_OK for success.
*/
-static nsgif_result nsgif__parse_frame_extensions(
+static nsgif_error nsgif__parse_frame_extensions(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
@@ -819,19 +810,19 @@ static nsgif_result nsgif__parse_frame_extensions(
GIF_EXT_APPLICATION = 0xff,
};
const uint8_t *nsgif_data = *pos;
- const uint8_t *nsgif_end = gif->nsgif_data + gif->buffer_size;
+ const uint8_t *nsgif_end = gif->buf + gif->buf_len;
int nsgif_bytes = nsgif_end - nsgif_data;
/* Initialise the extensions */
while (nsgif_bytes > 0 && nsgif_data[0] == GIF_EXT_INTRODUCER) {
bool block_step = true;
- nsgif_result ret;
+ nsgif_error ret;
nsgif_data++;
nsgif_bytes--;
if (nsgif_bytes == 0) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
/* Switch on extension label */
@@ -875,7 +866,7 @@ static nsgif_result nsgif__parse_frame_extensions(
* the extension size itself
*/
if (nsgif_bytes < 2) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
nsgif_data += 2 + nsgif_data[1];
}
@@ -885,7 +876,7 @@ static nsgif_result nsgif__parse_frame_extensions(
while (nsgif_data < nsgif_end && nsgif_data[0] != NSGIF_BLOCK_TERMINATOR) {
nsgif_data += nsgif_data[0] + 1;
if (nsgif_data >= nsgif_end) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
}
nsgif_data++;
@@ -924,14 +915,14 @@ static nsgif_result nsgif__parse_frame_extensions(
* \param[in] decode Whether to decode the image descriptor.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_image_descriptor(
+static nsgif_error nsgif__parse_image_descriptor(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
enum {
NSGIF_IMAGE_DESCRIPTOR_LEN = 10u,
NSGIF_IMAGE_SEPARATOR = 0x2Cu,
@@ -941,14 +932,14 @@ static nsgif_result nsgif__parse_image_descriptor(
assert(frame != NULL);
if (len < NSGIF_IMAGE_DESCRIPTOR_LEN) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (decode) {
uint32_t x, y, w, h;
if (data[0] != NSGIF_IMAGE_SEPARATOR) {
- return NSGIF_FRAME_DATA_ERROR;
+ return NSGIF_ERR_DATA_FRAME;
}
x = data[1] | (data[2] << 8);
@@ -957,10 +948,10 @@ static nsgif_result nsgif__parse_image_descriptor(
h = data[7] | (data[8] << 8);
frame->flags = data[9];
- frame->redraw.x0 = x;
- frame->redraw.y0 = y;
- frame->redraw.x1 = x + w;
- frame->redraw.y1 = y + h;
+ frame->info.rect.x0 = x;
+ frame->info.rect.y0 = y;
+ frame->info.rect.x1 = x + w;
+ frame->info.rect.y1 = y + h;
/* Allow first frame to grow image dimensions. */
if (gif->info.frame_count == 0) {
@@ -987,7 +978,7 @@ static nsgif_result nsgif__parse_image_descriptor(
* \param[in] decode Whether to decode the colour table.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__colour_table_extract(
+static nsgif_error nsgif__colour_table_extract(
struct nsgif *gif,
uint32_t colour_table[NSGIF_MAX_COLOURS],
size_t colour_table_entries,
@@ -995,10 +986,10 @@ static nsgif_result nsgif__colour_table_extract(
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < colour_table_entries * 3) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (decode) {
@@ -1036,13 +1027,13 @@ static nsgif_result nsgif__colour_table_extract(
* \param[in] decode Whether to decode the colour table.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_colour_table(
+static nsgif_error nsgif__parse_colour_table(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
bool decode)
{
- nsgif_result ret;
+ nsgif_error ret;
assert(gif != NULL);
assert(frame != NULL);
@@ -1074,17 +1065,17 @@ static nsgif_result nsgif__parse_colour_table(
* \param[in] decode Whether to decode the image data.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_image_data(
+static nsgif_error nsgif__parse_image_data(
struct nsgif *gif,
struct nsgif_frame *frame,
const uint8_t **pos,
bool decode)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
uint32_t frame_idx = frame - gif->frames;
uint8_t minimum_code_size;
- nsgif_result ret;
+ nsgif_error ret;
assert(gif != NULL);
assert(frame != NULL);
@@ -1103,12 +1094,12 @@ static nsgif_result nsgif__parse_image_data(
/* Fall through. */
case 1: if (data[0] == NSGIF_TRAILER) return NSGIF_OK;
/* Fall through. */
- case 0: return NSGIF_INSUFFICIENT_DATA;
+ case 0: return NSGIF_ERR_END_OF_DATA;
}
minimum_code_size = data[0];
if (minimum_code_size >= LZW_CODE_MAX) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA_FRAME;
}
if (decode) {
@@ -1121,7 +1112,7 @@ static nsgif_result nsgif__parse_image_data(
len--;
while (block_size != 1) {
- if (len < 1) return NSGIF_INSUFFICIENT_DATA;
+ if (len < 1) return NSGIF_ERR_END_OF_DATA;
block_size = data[0] + 1;
/* Check if the frame data runs off the end of the file */
if (block_size > len) {
@@ -1133,20 +1124,12 @@ static nsgif_result nsgif__parse_image_data(
data += block_size;
}
- gif->info.frame_count = frame_idx + 1;
- gif->frames[frame_idx].display = true;
*pos = data;
- /* Check if we've finished */
- if (len < 1) {
- return NSGIF_INSUFFICIENT_DATA;
- } else {
- if (data[0] == NSGIF_TRAILER) {
- return NSGIF_OK;
- }
- }
+ gif->info.frame_count = frame_idx + 1;
+ gif->frames[frame_idx].info.display = true;
- return NSGIF_WORKING;
+ return NSGIF_OK;
}
return ret;
@@ -1176,11 +1159,11 @@ static struct nsgif_frame *nsgif__get_frame(
frame->transparency = false;
frame->transparency_index = NSGIF_NO_TRANSPARENCY;
- frame->frame_pointer = gif->buffer_position;
+ frame->frame_pointer = gif->buf_pos;
frame->redraw_required = false;
- frame->disposal_method = 0;
- frame->frame_delay = 100;
- frame->display = false;
+ frame->info.display = false;
+ frame->info.disposal = 0;
+ frame->info.delay = 10;
frame->decoded = false;
}
@@ -1193,42 +1176,36 @@ static struct nsgif_frame *nsgif__get_frame(
* \param[in] gif The animation context
* \param[in] frame_idx The frame number to decode.
* \param[in] decode Whether to decode the graphical image data.
- * \return error code
- * - NSGIF_INSUFFICIENT_DATA reached unexpected end of source data.
- * - NSGIF_FRAME_DATA_ERROR for GIF frame data error
- * - NSGIF_INSUFFICIENT_MEMORY for insufficient memory to process
- * - NSGIF_DATA_ERROR for GIF error (invalid frame header)
- * - NSGIF_OK for successful decoding
- * - NSGIF_WORKING for successful decoding if more frames are expected
+ * \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__process_frame(
+static nsgif_error nsgif__process_frame(
struct nsgif *gif,
uint32_t frame_idx,
bool decode)
{
- nsgif_result ret;
+ nsgif_error ret;
const uint8_t *pos;
const uint8_t *end;
struct nsgif_frame *frame;
frame = nsgif__get_frame(gif, frame_idx);
if (frame == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
- end = gif->nsgif_data + gif->buffer_size;
+ end = gif->buf + gif->buf_len;
if (decode) {
- pos = gif->nsgif_data + frame->frame_pointer;
+ pos = gif->buf + frame->frame_pointer;
/* Ensure this frame is supposed to be decoded */
- if (frame->display == false) {
+ if (frame->info.display == false) {
return NSGIF_OK;
}
/* Ensure the frame is in range to decode */
if (frame_idx > gif->frame_count_partial) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
/* Done if frame is already decoded */
@@ -1236,7 +1213,7 @@ static nsgif_result nsgif__process_frame(
return NSGIF_OK;
}
} else {
- pos = (uint8_t *)(gif->nsgif_data + gif->buffer_position);
+ pos = (uint8_t *)(gif->buf + gif->buf_pos);
/* Check if we've finished */
if (pos < end && pos[0] == NSGIF_TRAILER) {
@@ -1247,7 +1224,7 @@ static nsgif_result nsgif__process_frame(
* millions of frames, so we ensure that we don't have a
* silly number. */
if (frame_idx > 4096) {
- return NSGIF_FRAME_DATA_ERROR;
+ return NSGIF_ERR_FRAME_COUNT;
}
}
@@ -1273,14 +1250,14 @@ static nsgif_result nsgif__process_frame(
cleanup:
if (!decode) {
- gif->buffer_position = pos - gif->nsgif_data;
+ gif->buf_pos = pos - gif->buf;
}
return ret;
}
/* exported function documented in nsgif.h */
-void nsgif_destroy(nsgif *gif)
+void nsgif_destroy(nsgif_t *gif)
{
if (gif == NULL) {
return;
@@ -1306,20 +1283,20 @@ void nsgif_destroy(nsgif *gif)
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
+nsgif_error nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif_t **gif_out)
{
- nsgif *gif;
+ nsgif_t *gif;
gif = calloc(1, sizeof(*gif));
if (gif == NULL) {
- return NSGIF_INSUFFICIENT_MEMORY;
+ return NSGIF_ERR_OOM;
}
gif->bitmap = *bitmap_vt;
gif->decoded_frame = NSGIF_FRAME_INVALID;
gif->prev_index = NSGIF_FRAME_INVALID;
- gif->info.delay_min = 2;
+ gif->delay_min = 2;
gif->delay_default = 10;
*gif_out = gif;
@@ -1339,27 +1316,27 @@ nsgif_result nsgif_create(const nsgif_bitmap_cb_vt *bitmap_vt, nsgif **gif_out)
* \param[in] strict Whether to require a known GIF version.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_header(
+static nsgif_error nsgif__parse_header(
struct nsgif *gif,
const uint8_t **pos,
bool strict)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < 6) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
if (strncmp((const char *) data, "GIF", 3) != 0) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA;
}
data += 3;
if (strict == true) {
if ((strncmp((const char *) data, "87a", 3) != 0) &&
(strncmp((const char *) data, "89a", 3) != 0)) {
- return NSGIF_DATA_ERROR;
+ return NSGIF_ERR_DATA;
}
}
data += 3;
@@ -1387,15 +1364,15 @@ static nsgif_result nsgif__parse_header(
* \param[in,out] pos The current buffer position, updated on success.
* \return NSGIF_OK on success, appropriate error otherwise.
*/
-static nsgif_result nsgif__parse_logical_screen_descriptor(
+static nsgif_error nsgif__parse_logical_screen_descriptor(
struct nsgif *gif,
const uint8_t **pos)
{
const uint8_t *data = *pos;
- size_t len = gif->nsgif_data + gif->buffer_size - data;
+ size_t len = gif->buf + gif->buf_len - data;
if (len < 7) {
- return NSGIF_INSUFFICIENT_DATA;
+ return NSGIF_ERR_END_OF_DATA;
}
gif->info.width = data[0] | (data[1] << 8);
@@ -1411,23 +1388,24 @@ static nsgif_result nsgif__parse_logical_screen_descriptor(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_data_scan(
- nsgif *gif,
+nsgif_error nsgif_data_scan(
+ nsgif_t *gif,
size_t size,
const uint8_t *data)
{
const uint8_t *nsgif_data;
- nsgif_result ret;
+ nsgif_error ret;
+ uint32_t frames;
/* Initialize values */
- gif->buffer_size = size;
- gif->nsgif_data = data;
+ gif->buf_len = size;
+ gif->buf = data;
/* Get our current processing position */
- nsgif_data = gif->nsgif_data + gif->buffer_position;
+ nsgif_data = gif->buf + gif->buf_pos;
/* See if we should initialise the GIF */
- if (gif->buffer_position == 0) {
+ if (gif->buf_pos == 0) {
/* We want everything to be NULL before we start so we've no
* chance of freeing bad pointers (paranoia)
*/
@@ -1452,7 +1430,7 @@ nsgif_result nsgif_data_scan(
}
/* Remember we've done this now */
- gif->buffer_position = nsgif_data - gif->nsgif_data;
+ gif->buf_pos = nsgif_data - gif->buf;
/* Some broken GIFs report the size as the screen size they
* were created in. As such, we detect for the common cases and
@@ -1480,7 +1458,7 @@ nsgif_result nsgif_data_scan(
* termination block) Although generally useless, the GIF
* specification does not expressly prohibit this
*/
- if (gif->buffer_size == gif->buffer_position + 1) {
+ if (gif->buf_len == gif->buf_pos + 1) {
if (nsgif_data[0] == NSGIF_TRAILER) {
return NSGIF_OK;
}
@@ -1502,7 +1480,7 @@ nsgif_result nsgif_data_scan(
return ret;
}
- gif->buffer_position = (nsgif_data - gif->nsgif_data);
+ gif->buf_pos = (nsgif_data - gif->buf);
} else {
/* Create a default colour table with the first two
* colours as black and white
@@ -1533,15 +1511,22 @@ nsgif_result nsgif_data_scan(
}
}
- /* Repeatedly try to initialise frames */
+ /* Try to initialise all frames. */
do {
- ret = nsgif__process_frame(gif, gif->info.frame_count, false);
- } while (ret == NSGIF_WORKING);
+ frames = gif->info.frame_count;
+ ret = nsgif__process_frame(gif, frames, false);
+ } while (gif->info.frame_count > frames);
+
+ if (ret == NSGIF_ERR_END_OF_DATA && gif->info.frame_count > 0) {
+ ret = NSGIF_OK;
+ }
return ret;
}
-static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redraw)
+static void nsgif__redraw_rect_extend(
+ const nsgif_rect_t *frame,
+ nsgif_rect_t *redraw)
{
if (redraw->x1 == 0 || redraw->y1 == 0) {
*redraw = *frame;
@@ -1562,7 +1547,7 @@ static void nsgif__redraw_rect_extend(const nsgif_rect *frame, nsgif_rect *redra
}
static uint32_t nsgif__frame_next(
- nsgif *gif,
+ nsgif_t *gif,
bool partial,
uint32_t frame)
{
@@ -1578,8 +1563,8 @@ static uint32_t nsgif__frame_next(
return (frame >= frames) ? 0 : frame;
}
-static nsgif_result nsgif__next_displayable_frame(
- nsgif *gif,
+static nsgif_error nsgif__next_displayable_frame(
+ nsgif_t *gif,
uint32_t *frame,
uint32_t *delay)
{
@@ -1588,14 +1573,14 @@ static nsgif_result nsgif__next_displayable_frame(
do {
next = nsgif__frame_next(gif, false, next);
if (next == *frame || next == NSGIF_FRAME_INVALID) {
- return NSGIF_FRAME_NO_DISPLAY;
+ return NSGIF_ERR_FRAME_DISPLAY;
}
if (delay != NULL) {
- *delay += gif->frames[next].frame_delay;
+ *delay += gif->frames[next].info.delay;
}
- } while (gif->frames[next].display == false);
+ } while (gif->frames[next].info.display == false);
*frame = next;
return NSGIF_OK;
@@ -1610,8 +1595,8 @@ static inline bool nsgif__animation_complete(int count, int max)
return (count >= max);
}
-nsgif_result nsgif_reset(
- nsgif *gif)
+nsgif_error nsgif_reset(
+ nsgif_t *gif)
{
gif->info.loop_count = 0;
gif->frame = NSGIF_FRAME_INVALID;
@@ -1620,35 +1605,33 @@ nsgif_result nsgif_reset(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_frame_prepare(
- nsgif *gif,
- nsgif_rect *area,
+nsgif_error nsgif_frame_prepare(
+ nsgif_t *gif,
+ nsgif_rect_t *area,
uint32_t *delay_cs,
uint32_t *frame_new)
{
- nsgif_result ret;
- nsgif_rect rect = {
+ nsgif_error ret;
+ nsgif_rect_t rect = {
.x1 = 0,
.y1 = 0,
};
uint32_t delay = 0;
uint32_t frame = gif->frame;
- uint32_t frame_next;
if (gif->frame != NSGIF_FRAME_INVALID &&
- gif->frame != 0 &&
gif->frame < gif->info.frame_count &&
- gif->frames[gif->frame].display) {
- rect = gif->frames[gif->frame].redraw;
+ gif->frames[gif->frame].info.display) {
+ rect = gif->frames[gif->frame].info.rect;
}
if (nsgif__animation_complete(
gif->info.loop_count,
gif->info.loop_max)) {
- return NSGIF_ANIMATION_COMPLETE;
+ return NSGIF_ERR_ANIMATION_END;
}
- ret = nsgif__next_displayable_frame(gif, &frame, NULL);
+ ret = nsgif__next_displayable_frame(gif, &frame, &delay);
if (ret != NSGIF_OK) {
return ret;
}
@@ -1657,22 +1640,27 @@ nsgif_result nsgif_frame_prepare(
gif->info.loop_count++;
}
- frame_next = frame;
- ret = nsgif__next_displayable_frame(gif, &frame_next, &delay);
- if (ret != NSGIF_OK) {
- return ret;
- }
+ if (gif->info.frame_count == 1) {
+ delay = NSGIF_INFINITE;
- if (frame_next < frame) {
- if (nsgif__animation_complete(
- gif->info.loop_count + 1,
- gif->info.loop_max)) {
- delay = NSGIF_INFINITE;
+ } else if (gif->info.loop_max != 0) {
+ uint32_t frame_next = frame;
+ ret = nsgif__next_displayable_frame(gif, &frame_next, NULL);
+ if (ret != NSGIF_OK) {
+ return ret;
+ }
+
+ if (frame_next < frame) {
+ if (nsgif__animation_complete(
+ gif->info.loop_count + 1,
+ gif->info.loop_max)) {
+ delay = NSGIF_INFINITE;
+ }
}
}
gif->frame = frame;
- nsgif__redraw_rect_extend(&gif->frames[frame].redraw, &rect);
+ nsgif__redraw_rect_extend(&gif->frames[frame].info.rect, &rect);
*frame_new = gif->frame;
*delay_cs = delay;
@@ -1682,13 +1670,17 @@ nsgif_result nsgif_frame_prepare(
}
/* exported function documented in nsgif.h */
-nsgif_result nsgif_frame_decode(
- nsgif *gif,
+nsgif_error nsgif_frame_decode(
+ nsgif_t *gif,
uint32_t frame,
nsgif_bitmap_t **bitmap)
{
uint32_t start_frame;
- nsgif_result ret = NSGIF_OK;
+ nsgif_error ret = NSGIF_OK;
+
+ if (frame > gif->info.frame_count) {
+ return NSGIF_ERR_BAD_FRAME;
+ }
if (gif->decoded_frame == frame) {
*bitmap = gif->frame_image;
@@ -1714,7 +1706,61 @@ nsgif_result nsgif_frame_decode(
return ret;
}
-const nsgif_info_t *nsgif_get_info(const nsgif *gif)
+/* exported function documented in nsgif.h */
+const nsgif_info_t *nsgif_get_info(const nsgif_t *gif)
{
return &gif->info;
}
+
+/* exported function documented in nsgif.h */
+const nsgif_frame_info_t *nsgif_get_frame_info(
+ const nsgif_t *gif,
+ uint32_t frame)
+{
+ if (frame > gif->info.frame_count) {
+ return NULL;
+ }
+
+ return &gif->frames[frame].info;
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_strerror(nsgif_error err)
+{
+ static const char *const str[] = {
+ [NSGIF_OK] = "Success",
+ [NSGIF_ERR_OOM] = "Out of memory",
+ [NSGIF_ERR_DATA] = "Invalid source data",
+ [NSGIF_ERR_BAD_FRAME] = "Requested frame does not exist",
+ [NSGIF_ERR_DATA_FRAME] = "Invalid frame data",
+ [NSGIF_ERR_FRAME_COUNT] = "Excessive number of frames",
+ [NSGIF_ERR_END_OF_DATA] = "Insufficient data for first frame",
+ [NSGIF_ERR_END_OF_FRAME] = "End of data during frame",
+ [NSGIF_ERR_FRAME_DISPLAY] = "Frame can't be displayed",
+ [NSGIF_ERR_ANIMATION_END] = "Animation complete",
+ };
+
+ if (err >= NSGIF_ARRAY_LEN(str) || str[err] == NULL) {
+ return "Unknown error";
+ }
+
+ return str[err];
+}
+
+/* exported function documented in nsgif.h */
+const char *nsgif_str_disposal(enum nsgif_disposal disposal)
+{
+ static const char *const str[] = {
+ [NSGIF_DISPOSAL_UNSPECIFIED] = "Unspecified",
+ [NSGIF_DISPOSAL_NONE] = "None",
+ [NSGIF_DISPOSAL_RESTORE_BG] = "Restore background",
+ [NSGIF_DISPOSAL_RESTORE_PREV] = "Restore previous",
+ [NSGIF_DISPOSAL_RESTORE_QUIRK] = "Restore quirk",
+ };
+
+ if (disposal >= NSGIF_ARRAY_LEN(str) || str[disposal] == NULL) {
+ return "Unspecified";
+ }
+
+ return str[disposal];
+}
diff --git a/test/Makefile b/test/Makefile
index f067d81..a578aef 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,3 +1,3 @@
-DIR_TEST_ITEMS := decode_gif:decode_gif.c
+DIR_TEST_ITEMS := nsgif:nsgif.c
include $(NSBUILD)/Makefile.subdir
diff --git a/test/cli.c b/test/cli.c
new file mode 100644
index 0000000..031cd97
--- /dev/null
+++ b/test/cli.c
@@ -0,0 +1,763 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cli.h"
+
+/**
+ * Check whether a CLI argument type should have a numerical value.
+ *
+ * \param[in] type An argument type.
+ * \return true if the argument needs a numerical value, or false otherwise.
+ */
+static inline bool cli__arg_is_numerical(enum cli_arg_type type)
+{
+ return (type != CLI_STRING && type != CLI_BOOL);
+}
+
+/**
+ * Parse a signed integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] i Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_int(
+ const char *str,
+ int64_t *i,
+ size_t *pos)
+{
+ long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoll(str, &end, 0);
+
+ if (end == str || errno == ERANGE ||
+ temp > INT64_MAX || temp < INT64_MIN) {
+ fprintf(stderr, "Failed to parse integer from '%s'\n", str);
+ return false;
+ }
+
+ *i = (int64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an unsigned integer value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] u Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_uint(
+ const char *str,
+ uint64_t *u,
+ size_t *pos)
+{
+ unsigned long long temp;
+ char *end = NULL;
+
+ str += *pos;
+ errno = 0;
+ temp = strtoull(str, &end, 0);
+
+ if (end == str || errno == ERANGE || temp > UINT64_MAX) {
+ fprintf(stderr, "Failed to parse unsigned from '%s'\n", str);
+ return false;
+ }
+
+ *u = (uint64_t)temp;
+ *pos += (size_t)(end - str);
+ return true;
+}
+
+/**
+ * Parse an enum value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] e Enum details.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_enum(
+ const char *str,
+ const struct cli_enum *e,
+ size_t *pos)
+{
+ str += *pos;
+ *pos += strlen(str);
+
+ for (const struct cli_str_val *sv = e->desc; sv->str != NULL; sv++) {
+ if (strcmp(str, sv->str) == 0) {
+ *e->e = sv->val;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Parse a string value from an argument.
+ *
+ * \param[in] str String containing value to parse.
+ * \param[out] s Pointer to place to store parsed value.
+ * \param[in,out] pos Current position in str, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value_string(
+ const char *str,
+ const char **s,
+ size_t *pos)
+{
+ *s = str + *pos;
+ *pos += strlen(*s);
+ return true;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse a value from.
+ * \param[in,out] pos Current position in argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_value(
+ const struct cli_table_entry *entry,
+ const char *arg,
+ size_t *pos)
+{
+ switch (entry->t) {
+ case CLI_CMD:
+ if (strcmp(arg + *pos, entry->l) == 0) {
+ *pos += strlen(arg);
+ return true;
+ }
+ return false;
+
+ case CLI_INT:
+ return cli__parse_value_int(arg, entry->v.i, pos);
+
+ case CLI_UINT:
+ return cli__parse_value_uint(arg, entry->v.u, pos);
+
+ case CLI_ENUM:
+ return cli__parse_value_enum(arg, &entry->v.e, pos);
+
+ case CLI_STRING:
+ return cli__parse_value_string(arg, entry->v.s, pos);
+
+ default:
+ fprintf(stderr, "Unexpected value for '%s': %s\n",
+ entry->l, arg);
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Parse a value from an argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in] arg_pos Current position in argv.
+ * \param[in,out] pos Current pos in current argument, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_argv_value(const struct cli_table_entry *entry,
+ int argc, const char **argv,
+ int arg_pos, size_t *pos)
+{
+ const char *arg = argv[arg_pos];
+
+ if (arg_pos >= argc) {
+ fprintf(stderr, "Value not given for '%s'\n", entry->l);
+ return false;
+ }
+
+ return cli__parse_value(entry, arg, pos);
+}
+
+/**
+ * Check whether a CLI argument is a positional value.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \return true if the argument is positional, or false otherwise.
+ */
+static inline bool cli__entry_is_positional(const struct cli_table_entry *entry)
+{
+ return entry->p;
+}
+
+/**
+ * Look up a short argument flag.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] s Argument flag to look up in client CLI spec.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_short(
+ const struct cli_table *cli, char s)
+{
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ continue;
+ }
+ if (cli->entries[i].s == s) {
+ return &cli->entries[i];
+ }
+ }
+
+ fprintf(stderr, "Unknown flag: '%c'\n", s);
+ return NULL;
+}
+
+/**
+ * Handle an argument with a type that requires a value.
+ *
+ * This can handle the value being in the current argument, optionally split by
+ * a separator, or in the next argument.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[in,out] arg_pos Current position in argv, updated on exit.
+ * \param[in] pos Current position in current argument string.
+ * \param[in] sep Name/value separator character, or '\0' if none.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__handle_arg_value(const struct cli_table_entry *entry,
+ int argc, const char **argv, int *arg_pos, size_t pos, char sep)
+{
+ const char *arg = argv[*arg_pos];
+ size_t orig_pos;
+ bool ret;
+
+ if (arg[pos] == '\0') {
+ (*arg_pos)++;
+ pos = 0;
+ } else if (arg[pos] == sep) {
+ pos++;
+ } else if (cli__arg_is_numerical(entry->t) == false) {
+ fprintf(stderr, "Separator required for non-numerical value\n");
+ return false;
+ }
+
+ if (isspace(argv[*arg_pos][pos])) {
+ fprintf(stderr, "Unexpected white space in '%s' "
+ "for argument '%s'\n",
+ &argv[*arg_pos][pos], entry->l);
+ return false;
+ }
+
+ orig_pos = pos;
+ ret = cli__parse_argv_value(entry, argc, argv, *arg_pos, &pos);
+ if (ret != true) {
+ return ret;
+ }
+
+ if (argv[*arg_pos][pos] != '\0') {
+ fprintf(stderr, "Invalid value '%s' for argument '%s'\n",
+ &argv[*arg_pos][orig_pos], entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a flags argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_short(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const char *arg = argv[*arg_pos];
+ size_t pos = 1;
+
+ if (arg[0] != '-') {
+ return false;
+ }
+
+ while (arg[pos] != '\0') {
+ const struct cli_table_entry *entry;
+
+ entry = cli__lookup_short(cli, arg[pos]);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ *entry->v.b = true;
+ } else {
+ return cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos + 1, '\0');
+ }
+
+ pos++;
+ }
+
+ return true;
+}
+
+/**
+ * Look up a long argument name.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument name to look up in cli spec.
+ * \param[in,out] pos Current position in arg, updated on exit.
+ * \return Client CLI spec entry on success, or NULL otherwise.
+ */
+static const struct cli_table_entry *cli__lookup_long(
+ const struct cli_table *cli,
+ const char *arg,
+ size_t *pos)
+{
+ arg += *pos;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i]) == false) {
+ const char *name = cli->entries[i].l;
+ size_t name_len = strlen(cli->entries[i].l);
+
+ if (strncmp(name, arg, name_len) == 0) {
+ if (arg[name_len] != '\0' &&
+ arg[name_len] != '=') {
+ continue;
+ }
+ *pos += name_len;
+ return &cli->entries[i];
+ }
+ }
+ }
+
+ fprintf(stderr, "Unknown argument: '%s'\n", arg);
+ return NULL;
+}
+
+/**
+ * Parse a long argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \param[out] arg_pos Current position in argv, updated on exit.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_long(const struct cli_table *cli,
+ int argc, const char **argv, int *arg_pos)
+{
+ const struct cli_table_entry *entry;
+ const char *arg = argv[*arg_pos];
+ size_t pos = 2;
+
+ if (arg[0] != '-' ||
+ arg[1] != '-') {
+ return false;
+ }
+
+ entry = cli__lookup_long(cli, arg, &pos);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->t == CLI_BOOL) {
+ if (arg[pos] != '\0') {
+ fprintf(stderr, "Unexpected value for argument '%s'\n",
+ arg);
+ return false;
+ }
+ *entry->v.b = true;
+ } else {
+ bool ret;
+
+ ret = cli__handle_arg_value(entry, argc, argv,
+ arg_pos, pos, '=');
+ if (ret != true) {
+ return ret;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument according to the given CLI spec entry.
+ *
+ * \param[in] entry Client command line interface argument specification.
+ * \param[in] arg Argument to parse.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional_entry(
+ const struct cli_table_entry *entry,
+ const char *arg)
+{
+ size_t pos = 0;
+ bool ret;
+
+ ret = cli__parse_value(entry, arg, &pos);
+ if (ret != true) {
+ return ret;
+ } else if (arg[pos] != '\0') {
+ fprintf(stderr, "Failed to parse value '%s' for arg '%s'\n",
+ arg, entry->l);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a positional argument.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] arg Argument to parse.
+ * \param[in] count Number of positional arguments parsed already.
+ * \return true on success, or false otherwise.
+ */
+static bool cli__parse_positional(const struct cli_table *cli,
+ const char *arg, size_t count)
+{
+ size_t positional = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ if (positional == count) {
+ return cli__parse_positional_entry(
+ &cli->entries[i], arg);
+ }
+
+ positional++;
+ }
+ }
+
+ fprintf(stderr, "Unexpected positional argument: '%s'\n", arg);
+ return false;
+}
+
+/**
+ * Get the string to indicate type of value expected for an argument.
+ *
+ * \param[in] type The argument type.
+ * \return String for value type.
+ */
+static const char *cli__string_from_type(enum cli_arg_type type)
+{
+ static const char *const strings[] = {
+ [CLI_BOOL] = "",
+ [CLI_INT] = "INT",
+ [CLI_UINT] = "UINT",
+ [CLI_ENUM] = "ENUM",
+ [CLI_STRING] = "STRING",
+ };
+
+ if (type >= CLI_ARRAY_LEN(strings) || strings[type] == NULL) {
+ return "";
+ }
+
+ return strings[type];
+}
+
+/**
+ * Helper to update a maximum adjusted string length if new values is greater.
+ *
+ * \param[in] str String to check.
+ * \param[in] adjustment Amount to modify length of string by (bytes).
+ * \param[out] len Returns the maximum of existing and this length.
+ */
+static void cli__max_len(const char *str, size_t adjustment, size_t *len)
+{
+ size_t str_len = strlen(str) + adjustment;
+
+ if (str_len > *len) {
+ *len = str_len;
+ }
+}
+
+/**
+ * Count up various properties of the client CLI interface specification.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[out] count Returns number of non-positional arguments.
+ * \param[out] pcount Returns number of positional arguments.
+ * \param[out] max_len Returns max string length of non-positional arguments.
+ * \param[out] pmax_len Returns max string length of positional arguments.
+ * \param[out] phas_desc Returns number of positional args with descriptions.
+ */
+static void cli__count(const struct cli_table *cli,
+ size_t *count,
+ size_t *pcount,
+ size_t *max_len,
+ size_t *pmax_len,
+ size_t *phas_desc)
+{
+ if (count != NULL) *count = 0;
+ if (pcount != NULL) *pcount = 0;
+ if (max_len != NULL) *max_len = 0;
+ if (pmax_len != NULL) *pmax_len = 0;
+ if (phas_desc != NULL) *phas_desc = 0;
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (cli__entry_is_positional(entry)) {
+ if (pcount != NULL) {
+ (*pcount)++;
+ }
+ if (pmax_len != NULL) {
+ cli__max_len(entry->l, 0, pmax_len);
+ }
+ if (phas_desc != NULL) {
+ (*phas_desc)++;
+ }
+ } else {
+ if (count != NULL) {
+ (*count)++;
+ }
+ if (max_len != NULL) {
+ const char *type_str;
+ size_t type_len;
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+
+ cli__max_len(entry->l, type_len, max_len);
+ }
+ }
+ }
+}
+
+static inline bool cli__is_negative(const char *arg)
+{
+ int64_t i;
+ size_t pos = 0;
+
+ return cli__parse_value_int(arg, &i, &pos)
+ && pos == strlen(arg)
+ && i < 0;
+}
+
+/* Documented in cli.h */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv)
+{
+ size_t pos_count = 0;
+ enum {
+ ARG_PROG_NAME,
+ ARG_FIRST,
+ };
+
+ for (int i = ARG_FIRST; i < argc; i++) {
+ const char *arg = argv[i];
+ size_t pos_inc = 0;
+ bool ret;
+
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ ret = cli__parse_long(cli, argc, argv, &i);
+ } else {
+ ret = cli__parse_short(cli, argc, argv, &i);
+ if (ret != true) {
+ if (cli__is_negative(argv[i])) {
+ pos_inc = 1;
+ ret = cli__parse_positional(
+ cli, argv[i],
+ pos_count);
+ }
+ }
+ }
+ } else {
+ pos_inc = 1;
+ ret = cli__parse_positional(cli, argv[i], pos_count);
+ }
+
+ if (ret != true) {
+ return ret;
+ }
+
+ pos_count += pos_inc;
+ }
+
+ if (pos_count < cli->min_positional) {
+ fprintf(stderr, "Insufficient positional arguments found.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Get terminal width.
+ *
+ * \return terminal width in characters.
+ */
+static size_t cli__terminal_width(void)
+{
+ return 80;
+}
+
+/**
+ * Print an entry's description, with a given indent.
+ *
+ * The indent is assumed to already be applied for the first line of the
+ * output by the caller.
+ *
+ * \param[in] entry The entry to print the description for.
+ * \param[in] indent The number of spaces to pad the left margin with.
+ */
+static void cli__print_description(const struct cli_table_entry *entry,
+ size_t indent)
+{
+ size_t terminal_width = cli__terminal_width();
+ size_t avail = (indent > terminal_width) ? 0 : terminal_width - indent;
+ size_t space = avail;
+ const char *desc = entry->d;
+
+ if (desc != NULL) {
+ while (*desc != '\0') {
+ size_t word_len = strcspn(desc, " \n\t");
+ if (word_len <= space || space == avail) {
+ fprintf(stderr, "%*.*s",
+ (int)word_len,
+ (int)word_len, desc);
+ desc += word_len;
+ if (word_len <= space) {
+ space -= word_len;
+ }
+ if (space > 0) {
+ fprintf(stderr, " ");
+ space--;
+ }
+ } else {
+ fprintf(stderr, "\n%*s", (int)indent, "");
+ space = avail;
+ }
+ desc += strspn(desc, " \n\t");
+ }
+ }
+
+ fprintf(stderr, "\n");
+}
+
+/* Documented in cli.h */
+void cli_help(const struct cli_table *cli, const char *prog_name)
+{
+ size_t count;
+ size_t pcount;
+ size_t max_len;
+ size_t pmax_len;
+ size_t phas_desc;
+ size_t required = 0;
+ enum {
+ ARG_PROG_NAME,
+ };
+
+ cli__count(cli, &count, &pcount, &max_len, &pmax_len, &phas_desc);
+
+ fprintf(stderr, "\nUsage: %s", prog_name);
+
+ if (pcount > 0) {
+ for (size_t i = 0; i < cli->count; i++) {
+ if (cli__entry_is_positional(&cli->entries[i])) {
+ const char *punctuation =
+ (required == cli->min_positional) ?
+ " [" : " ";
+
+ if (cli->entries[i].t == CLI_CMD) {
+ fprintf(stderr, "%s%s", punctuation,
+ cli->entries[i].l);
+ } else {
+ fprintf(stderr, "%s<%s>", punctuation,
+ cli->entries[i].l);
+ }
+ required++;
+ }
+ }
+ if (required == pcount && required > cli->min_positional) {
+ fprintf(stderr, "]");
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, " [options]");
+ }
+
+ fprintf(stderr, "\n\n");
+
+ if (phas_desc > 0) {
+ fprintf(stderr, "Where:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+
+ if (entry->d == NULL) {
+ continue;
+ }
+
+ if (cli__entry_is_positional(entry)) {
+ fprintf(stderr, " %*.*s ",
+ (int)pmax_len,
+ (int)pmax_len,
+ entry->l);
+ cli__print_description(entry, pmax_len + 4);
+ fprintf(stderr, "\n");
+ }
+ }
+ }
+
+ if (count > 0) {
+ fprintf(stderr, "Options:\n\n");
+
+ for (size_t i = 0; i < cli->count; i++) {
+ const struct cli_table_entry *entry = &cli->entries[i];
+ const char *type_str;
+ size_t type_len;
+ size_t arg_len;
+
+ if (cli__entry_is_positional(entry)) {
+ continue;
+ }
+
+ if (entry->s != '\0') {
+ fprintf(stderr, " -%c", entry->s);
+ } else {
+ fprintf(stderr, " ");
+ }
+
+ type_str = cli__string_from_type(entry->t);
+ type_len = strlen(type_str);
+ arg_len = strlen(entry->l);
+
+ fprintf(stderr, " --%s %s%*.s ", entry->l, type_str,
+ (int)(max_len - arg_len - type_len),
+ "");
+ cli__print_description(entry, max_len + 11);
+ fprintf(stderr, "\n");
+ }
+ }
+}
diff --git a/test/cli.h b/test/cli.h
new file mode 100644
index 0000000..91db086
--- /dev/null
+++ b/test/cli.h
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (C) 2021 Michael Drake <tlsa(a)netsurf-browser.org>
+ */
+
+/**
+ * \file
+ * \brief Command line argument handling API.
+ */
+
+#ifndef _PELTAR_CLI_H_
+#define _PELTAR_CLI_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Helper to get element count for an array,
+ *
+ * \param[in] _a Array to get number of elements for.
+ */
+#define CLI_ARRAY_LEN(_a) ((sizeof(_a))/(sizeof(*(_a))))
+
+/**
+ * CLI argument type.
+ */
+enum cli_arg_type {
+ CLI_CMD, /**< A sub-command. Must match long argument name. */
+ CLI_BOOL, /**< Has no value; presence of flag indicates true. */
+ CLI_INT, /**< Has signed integer value. */
+ CLI_UINT, /**< Has unsigned integer value. */
+ CLI_ENUM, /**< Has enumeration value. */
+ CLI_STRING, /**< Has string value. */
+};
+
+struct cli_str_val {
+ const char *str;
+ int64_t val;
+};
+
+struct cli_enum {
+ const struct cli_str_val *desc;
+ int64_t *e; /**< Location to store \ref CLI_ENUM value. */
+};
+
+/**
+ * Client description for a command line argument.
+ */
+struct cli_table_entry {
+ const char *l; /**< Long argument name. */
+ const char s; /**< Short flag name. (Non-positional arguments.) */
+ bool p; /**< Whether the argument is a positional argument. */
+ enum cli_arg_type t; /**< Argument type. */
+ union {
+ bool *b; /**< Location to store \ref CLI_BOOL value. */
+ int64_t *i; /**< Location to store \ref CLI_INT value. */
+ uint64_t *u; /**< Location to store \ref CLI_UINT value. */
+ const char **s; /**< Location to store \ref CLI_STRING value. */
+ struct cli_enum e;
+ } v; /**< Where to store type-specific values. */
+ const char *d; /**< Description. */
+};
+
+/**
+ * Client command line interface specification.
+ */
+struct cli_table {
+ const struct cli_table_entry *entries;
+ size_t count;
+ size_t min_positional;
+};
+
+/**
+ * Parse the command line arguments.
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] argc Number of command line arguments.
+ * \param[in] argv String vector containing command line arguments.
+ * \return true on success, false on error.
+ */
+bool cli_parse(const struct cli_table *cli, int argc, const char **argv);
+
+/**
+ * Print usage and help output.
+ *
+ * Note: Assumes non-Unicode. (One byte per character.)
+ *
+ * \param[in] cli Client command line interface specification.
+ * \param[in] prog_name Program name.
+ */
+void cli_help(const struct cli_table *cli, const char *prog_name);
+
+#endif
diff --git a/test/decode_gif.c b/test/decode_gif.c
deleted file mode 100644
index acc2ef0..0000000
--- a/test/decode_gif.c
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
- * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
- *
- * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
- * Licenced under the MIT License,
- * http://www.opensource.org/licenses/mit-license.php
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#include "../include/nsgif.h"
-
-#define BYTES_PER_PIXEL 4
-
-static void *bitmap_create(int width, int height)
-{
- /* Ensure a stupidly large bitmap is not created */
- if (width > 4096 || height > 4096) {
- return NULL;
- }
-
- return calloc(width * height, BYTES_PER_PIXEL);
-}
-
-static unsigned char *bitmap_get_buffer(void *bitmap)
-{
- return bitmap;
-}
-
-static void bitmap_destroy(void *bitmap)
-{
- free(bitmap);
-}
-
-static uint8_t *load_file(const char *path, size_t *data_size)
-{
- FILE *fd;
- struct stat sb;
- unsigned char *buffer;
- size_t size;
- size_t n;
-
- fd = fopen(path, "rb");
- if (!fd) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- if (stat(path, &sb)) {
- perror(path);
- exit(EXIT_FAILURE);
- }
- size = sb.st_size;
-
- buffer = malloc(size);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %lld bytes\n",
- (long long) size);
- exit(EXIT_FAILURE);
- }
-
- n = fread(buffer, 1, size, fd);
- if (n != size) {
- perror(path);
- exit(EXIT_FAILURE);
- }
-
- fclose(fd);
-
- *data_size = size;
- return buffer;
-}
-
-static void warning(const char *context, nsgif_result gif_res)
-{
- fprintf(stderr, "%s failed: ", context);
- switch (gif_res) {
- case NSGIF_FRAME_DATA_ERROR:
- fprintf(stderr, "NSGIF_FRAME_DATA_ERROR");
- break;
- case NSGIF_INSUFFICIENT_DATA:
- fprintf(stderr, "NSGIF_INSUFFICIENT_DATA");
- break;
- case NSGIF_DATA_ERROR:
- fprintf(stderr, "NSGIF_DATA_ERROR");
- break;
- case NSGIF_INSUFFICIENT_MEMORY:
- fprintf(stderr, "NSGIF_INSUFFICIENT_MEMORY");
- break;
- default:
- fprintf(stderr, "unknown code %i", gif_res);
- break;
- }
- fprintf(stderr, "\n");
-}
-
-static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
-{
- nsgif_result gif_res;
- uint32_t frame_prev = 0;
- const nsgif_info_t *info;
-
- info = nsgif_get_info(gif);
-
- if (write_ppm) {
- fprintf(fh, "P3\n");
- fprintf(fh, "# %s\n", name);
- fprintf(fh, "# width %u \n", info->width);
- fprintf(fh, "# height %u \n", info->height);
- fprintf(fh, "# frame_count %u \n", info->frame_count);
- fprintf(fh, "# loop_max %u \n", info->loop_max);
- fprintf(fh, "%u %u 256\n", info->width,
- info->height * info->frame_count);
- }
-
- /* decode the frames */
- while (true) {
- nsgif_bitmap_t *buffer;
- const uint8_t *image;
- uint32_t frame_new;
- uint32_t delay_cs;
- nsgif_rect area;
-
- gif_res = nsgif_frame_prepare(gif, &area,
- &delay_cs, &frame_new);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_frame_prepare", gif_res);
- return;
- }
-
- if (frame_new < frame_prev) {
- /* Must be an animation that loops. We only care about
- * decoding each frame once. */
- return;
- }
- frame_prev = frame_new;
-
- gif_res = nsgif_frame_decode(gif, frame_new, &buffer);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_decode_frame", gif_res);
- return;
- }
-
- if (write_ppm) {
- fprintf(fh, "# frame %u:\n", frame_new);
- image = (const uint8_t *) buffer;
- for (uint32_t y = 0; y != info->height; y++) {
- for (uint32_t x = 0; x != info->width; x++) {
- size_t z = (y * info->width + x) * 4;
- fprintf(fh, "%u %u %u ",
- image[z],
- image[z + 1],
- image[z + 2]);
- }
- fprintf(fh, "\n");
- }
- }
- }
-}
-
-int main(int argc, char *argv[])
-{
- const nsgif_bitmap_cb_vt bitmap_callbacks = {
- .create = bitmap_create,
- .destroy = bitmap_destroy,
- .get_buffer = bitmap_get_buffer,
- };
- nsgif *gif;
- size_t size;
- uint8_t *data;
- FILE *outf = stdout;
- nsgif_result gif_res;
- bool no_write = false;
-
- if (argc < 2) {
- fprintf(stderr, "Usage: %s image.gif [out]\n", argv[0]);
- fprintf(stderr, "\n");
- fprintf(stderr, "If [out] is NOWRITE, the gif will be docoded "
- "but not output.\n");
- fprintf(stderr, "Otherwise [out] is an output filename.\n");
- fprintf(stderr, "When [out] is unset, output is to stdout.\n");
-
- return 1;
- }
-
- if (argc > 2) {
- if (strcmp(argv[2], "NOWRITE") == 0) {
- no_write = true;
- } else {
- outf = fopen(argv[2], "w+");
- if (outf == NULL) {
- fprintf(stderr, "Unable to open %s for writing\n", argv[2]);
- return 2;
- }
- }
- }
-
- /* create our gif animation */
- gif_res = nsgif_create(&bitmap_callbacks, &gif);
- if (gif_res != NSGIF_OK) {
- return 1;
- }
-
- /* load file into memory */
- data = load_file(argv[1], &size);
-
- /* Scan the raw data */
- gif_res = nsgif_data_scan(gif, size, data);
- if (gif_res != NSGIF_OK) {
- warning("nsgif_data_scan", gif_res);
- nsgif_destroy(gif);
- free(data);
- return 1;
- }
-
- decode(outf, argv[1], gif, !no_write);
-
- if (argc > 2 && !no_write) {
- fclose(outf);
- }
-
- /* clean up */
- nsgif_destroy(gif);
- free(data);
-
- return 0;
-}
diff --git a/test/nsgif.c b/test/nsgif.c
new file mode 100644
index 0000000..06defb9
--- /dev/null
+++ b/test/nsgif.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2008 Sean Fox <dyntryx(a)gmail.com>
+ * Copyright 2008 James Bursa <james(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
+ * Licenced under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "../include/nsgif.h"
+
+#include "cli.h"
+#include "cli.c"
+
+#define BYTES_PER_PIXEL 4
+
+static struct nsgif_options {
+ const char *file;
+ const char *ppm;
+ uint64_t loops;
+ bool info;
+} nsgif_options;
+
+static const struct cli_table_entry cli_entries[] = {
+ {
+ .s = 'm',
+ .l = "ppm",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.ppm,
+ .d = "Convert frames to PPM image at given path."
+ },
+ {
+ .s = 'i',
+ .l = "info",
+ .t = CLI_BOOL,
+ .v.b = &nsgif_options.info,
+ .d = "Dump GIF info to stdout."
+ },
+ {
+ .s = 'l',
+ .l = "loops",
+ .t = CLI_UINT,
+ .v.u = &nsgif_options.loops,
+ .d = "Loop through decoding all frames N times. "
+ "The default is 1."
+ },
+ {
+ .p = true,
+ .l = "FILE",
+ .t = CLI_STRING,
+ .v.s = &nsgif_options.file,
+ .d = "Path to GIF file to load."
+ },
+};
+
+const struct cli_table cli = {
+ .entries = cli_entries,
+ .count = (sizeof(cli_entries))/(sizeof(*cli_entries)),
+ .min_positional = 1,
+};
+
+static void *bitmap_create(int width, int height)
+{
+ /* Ensure a stupidly large bitmap is not created */
+ if (width > 4096 || height > 4096) {
+ return NULL;
+ }
+
+ return calloc(width * height, BYTES_PER_PIXEL);
+}
+
+static unsigned char *bitmap_get_buffer(void *bitmap)
+{
+ return bitmap;
+}
+
+static void bitmap_destroy(void *bitmap)
+{
+ free(bitmap);
+}
+
+static uint8_t *load_file(const char *path, size_t *data_size)
+{
+ FILE *fd;
+ struct stat sb;
+ unsigned char *buffer;
+ size_t size;
+ size_t n;
+
+ fd = fopen(path, "rb");
+ if (!fd) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ if (stat(path, &sb)) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+ size = sb.st_size;
+
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %lld bytes\n",
+ (long long) size);
+ exit(EXIT_FAILURE);
+ }
+
+ n = fread(buffer, 1, size, fd);
+ if (n != size) {
+ perror(path);
+ exit(EXIT_FAILURE);
+ }
+
+ fclose(fd);
+
+ *data_size = size;
+ return buffer;
+}
+
+static void warning(const char *context, nsgif_error err)
+{
+ fprintf(stderr, "%s failed: %s\n",
+ context, nsgif_strerror(err));
+}
+
+static void print_gif_info(const nsgif_info_t *info)
+{
+ fprintf(stdout, "gif:\n");
+ fprintf(stdout, " width: %"PRIu32"\n", info->width);
+ fprintf(stdout, " height: %"PRIu32"\n", info->height);
+ fprintf(stdout, " max-loops: %"PRIu32"\n", info->loop_max);
+ fprintf(stdout, " frame-count: %"PRIu32"\n", info->frame_count);
+ fprintf(stdout, " frames:\n");
+}
+
+static void print_gif_frame_info(const nsgif_frame_info_t *info)
+{
+ const char *disposal = nsgif_str_disposal(info->disposal);
+
+ fprintf(stdout, " - disposal-method: %s\n", disposal);
+ fprintf(stdout, " display: %s\n", info->display ? "yes" : "no");
+ fprintf(stdout, " delay: %"PRIu32"\n", info->delay);
+ fprintf(stdout, " rect:\n");
+ fprintf(stdout, " x: %"PRIu32"\n", info->rect.x0);
+ fprintf(stdout, " y: %"PRIu32"\n", info->rect.y0);
+ fprintf(stdout, " w: %"PRIu32"\n", info->rect.x1 - info->rect.x0);
+ fprintf(stdout, " h: %"PRIu32"\n", info->rect.y1 - info->rect.y0);
+}
+
+static void decode(FILE* ppm, const char *name, nsgif_t *gif)
+{
+ nsgif_error err;
+ uint32_t frame_prev = 0;
+ const nsgif_info_t *info;
+
+ info = nsgif_get_info(gif);
+
+ if (ppm != NULL) {
+ fprintf(ppm, "P3\n");
+ fprintf(ppm, "# %s\n", name);
+ fprintf(ppm, "# width %u \n", info->width);
+ fprintf(ppm, "# height %u \n", info->height);
+ fprintf(ppm, "# frame_count %u \n", info->frame_count);
+ fprintf(ppm, "# loop_max %u \n", info->loop_max);
+ fprintf(ppm, "%u %u 256\n", info->width,
+ info->height * info->frame_count);
+ }
+
+ if (nsgif_options.info == true) {
+ print_gif_info(info);
+ }
+
+ /* decode the frames */
+ while (true) {
+ nsgif_bitmap_t *buffer;
+ const uint8_t *image;
+ uint32_t frame_new;
+ uint32_t delay_cs;
+ nsgif_rect_t area;
+
+ err = nsgif_frame_prepare(gif, &area,
+ &delay_cs, &frame_new);
+ if (err != NSGIF_OK) {
+ warning("nsgif_frame_prepare", err);
+ return;
+ }
+
+ if (frame_new < frame_prev) {
+ /* Must be an animation that loops. We only care about
+ * decoding each frame once. */
+ return;
+ }
+ frame_prev = frame_new;
+
+ err = nsgif_frame_decode(gif, frame_new, &buffer);
+ if (err != NSGIF_OK) {
+ warning("nsgif_decode_frame", err);
+ return;
+ }
+
+ if (nsgif_options.info == true) {
+ const nsgif_frame_info_t *f_info;
+
+ f_info = nsgif_get_frame_info(gif, frame_new);
+ assert(f_info != NULL);
+ print_gif_frame_info(f_info);
+ }
+
+ if (ppm != NULL) {
+ fprintf(ppm, "# frame %u:\n", frame_new);
+ image = (const uint8_t *) buffer;
+ for (uint32_t y = 0; y != info->height; y++) {
+ for (uint32_t x = 0; x != info->width; x++) {
+ size_t z = (y * info->width + x) * 4;
+ fprintf(ppm, "%u %u %u ",
+ image[z],
+ image[z + 1],
+ image[z + 2]);
+ }
+ fprintf(ppm, "\n");
+ }
+ }
+
+ if (delay_cs == NSGIF_INFINITE) {
+ return;
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const nsgif_bitmap_cb_vt bitmap_callbacks = {
+ .create = bitmap_create,
+ .destroy = bitmap_destroy,
+ .get_buffer = bitmap_get_buffer,
+ };
+ size_t size;
+ nsgif_t *gif;
+ uint8_t *data;
+ nsgif_error err;
+ FILE *ppm = NULL;
+
+ /* Override default options with any command line args */
+ if (!cli_parse(&cli, argc, (void *)argv)) {
+ cli_help(&cli, argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (nsgif_options.ppm != NULL) {
+ ppm = fopen(nsgif_options.ppm, "w+");
+ if (ppm == NULL) {
+ fprintf(stderr, "Unable to open %s for writing\n",
+ nsgif_options.ppm);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* create our gif animation */
+ err = nsgif_create(&bitmap_callbacks, &gif);
+ if (err != NSGIF_OK) {
+ warning("nsgif_create", err);
+ return EXIT_FAILURE;
+ }
+
+ /* load file into memory */
+ data = load_file(nsgif_options.file, &size);
+
+ /* Scan the raw data */
+ err = nsgif_data_scan(gif, size, data);
+ if (err != NSGIF_OK) {
+ warning("nsgif_data_scan", err);
+ nsgif_destroy(gif);
+ free(data);
+ return EXIT_FAILURE;
+ }
+
+ for (uint64_t i = 0; i < nsgif_options.loops; i++) {
+ decode((i == 0) ? ppm : NULL, nsgif_options.file, gif);
+ }
+
+ if (ppm != NULL) {
+ fclose(ppm);
+ }
+
+ /* clean up */
+ nsgif_destroy(gif);
+ free(data);
+
+ return 0;
+}
diff --git a/test/runtest.sh b/test/runtest.sh
index 05665a1..fd84847 100755
--- a/test/runtest.sh
+++ b/test/runtest.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh
# run test images through libnsgif and count results
@@ -20,25 +20,25 @@ GIFTESTS="${GIFTESTS} test/ns-afl-gif/*.gif"
gifdecode()
{
- OUTF=$(basename ${1} .gif)
- CMPF=$(dirname ${1})/${OUTF}.ppm
- echo "GIF:${1}" >> ${TEST_LOG}
- ${TEST_PATH}/test_decode_gif ${1} ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
- ECODE=$?
-
- echo "Exit code:${ECODE}" >> ${TEST_LOG}
- if [ "${ECODE}" -gt 0 ];then
- return ${ECODE}
- fi
-
- if [ -f "${CMPF}" ]; then
- cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
- if [ "$?" -ne 0 ]; then
- return 128
+ OUTF=$(basename ${1} .gif)
+ CMPF=$(dirname ${1})/${OUTF}.ppm
+ echo "GIF:${1}" >> ${TEST_LOG}
+ ${TEST_PATH}/test_nsgif ${1} --ppm ${TEST_OUT}/${OUTF}.ppm 2>> ${TEST_LOG}
+ ECODE=$?
+
+ echo "Exit code:${ECODE}" >> ${TEST_LOG}
+ if [ "${ECODE}" -gt 0 ];then
+ return ${ECODE}
fi
- fi
- return 0
+ if [ -f "${CMPF}" ]; then
+ cmp ${CMPF} ${TEST_OUT}/${OUTF}.ppm >> ${TEST_LOG} 2>> ${TEST_LOG}
+ if [ "$?" -ne 0 ]; then
+ return 128
+ fi
+ fi
+
+ return 0
}
GIFTESTTOTC=0
@@ -49,27 +49,27 @@ GIFTESTERRC=0
echo "Testing GIF decode"
for GIF in $(ls ${GIFTESTS});do
- GIFTESTTOTC=$((GIFTESTTOTC+1))
- #echo "${GIF}"
- gifdecode ${GIF}
- ECODE=$?
- if [ "${ECODE}" -gt 127 ];then
- GIFTESTERRC=$((GIFTESTERRC+1))
- echo "Error ${GIF}"
- else
- if [ "${ECODE}" -gt 0 ];then
- GIFTESTFAILC=$((GIFTESTFAILC+1))
+ GIFTESTTOTC=$((GIFTESTTOTC+1))
+ #echo "${GIF}"
+ gifdecode ${GIF}
+ ECODE=$?
+ if [ "${ECODE}" -gt 127 ];then
+ GIFTESTERRC=$((GIFTESTERRC+1))
+ echo "Error ${GIF}"
else
- GIFTESTPASSC=$((GIFTESTPASSC+1))
+ if [ "${ECODE}" -gt 0 ];then
+ GIFTESTFAILC=$((GIFTESTFAILC+1))
+ else
+ GIFTESTPASSC=$((GIFTESTPASSC+1))
+ fi
fi
- fi
done
echo "Tests:${GIFTESTTOTC} Pass:${GIFTESTPASSC} Fail:${GIFTESTFAILC} Error:${GIFTESTERRC}"
# exit code
if [ "${GIFTESTERRC}" -gt 0 ]; then
- exit 1
+ exit 1
fi
exit 0
--
NetSurf GIF Decoder
1 year, 7 months
libnsgif: branch tlsa/gif-api updated. release/0.2.1-138-gaad22e6
by NetSurf Browser Project
Gitweb links:
...log http://git.netsurf-browser.org/libnsgif.git/shortlog/aad22e646509b7393f89...
...commit http://git.netsurf-browser.org/libnsgif.git/commit/aad22e646509b7393f89b7...
...tree http://git.netsurf-browser.org/libnsgif.git/tree/aad22e646509b7393f89b77f...
The branch, tlsa/gif-api has been updated
discards a04e9ba76c39bf020212536e04d976a6370a1adf (commit)
discards c5a9b1d67688166391346b94b03e36c8089305f7 (commit)
discards d5d6e9057bccdb7e41e5243276b7a3e0a2748817 (commit)
via aad22e646509b7393f89b77f59894fba52031b7c (commit)
via 3fe60b931a6529a0196d3bf6374a3569c3e7daba (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (a04e9ba76c39bf020212536e04d976a6370a1adf)
\
N -- N -- N (aad22e646509b7393f89b77f59894fba52031b7c)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
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=aad22e646509b7393f...
commit aad22e646509b7393f89b77f59894fba52031b7c
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
API: Clean up client bitmap interface.
diff --git a/include/nsgif.h b/include/nsgif.h
index 11378d1..1472842 100644
--- a/include/nsgif.h
+++ b/include/nsgif.h
@@ -73,31 +73,66 @@ typedef struct nsgif_rect {
uint32_t y1;
} nsgif_rect;
-/* API for Bitmap callbacks */
-typedef void* (*nsgif_bitmap_cb_create)(int width, int height);
-typedef void (*nsgif_bitmap_cb_destroy)(void *bitmap);
-typedef uint8_t* (*nsgif_bitmap_cb_get_buffer)(void *bitmap);
-typedef void (*nsgif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
-typedef bool (*nsgif_bitmap_cb_test_opaque)(void *bitmap);
-typedef void (*nsgif_bitmap_cb_modified)(void *bitmap);
+/**
+ * Client bitmap type.
+ *
+ * These are client-created and destroyed, via the \ref bitmap callbacks,
+ * but they are owned by a \ref nsgif.
+ */
+typedef void nsgif_bitmap_t;
/** Bitmap callbacks function table */
typedef struct nsgif_bitmap_cb_vt {
- /** Create a bitmap. */
- nsgif_bitmap_cb_create create;
- /** Free a bitmap. */
- nsgif_bitmap_cb_destroy destroy;
- /** Return a pointer to the pixel data in a bitmap. */
- nsgif_bitmap_cb_get_buffer get_buffer;
-
- /* Members below are optional */
-
- /** Sets whether a bitmap should be plotted opaque. */
- nsgif_bitmap_cb_set_opaque set_opaque;
- /** Tests whether a bitmap has an opaque alpha channel. */
- nsgif_bitmap_cb_test_opaque test_opaque;
- /** The bitmap image has changed, so flush any persistent cache. */
- nsgif_bitmap_cb_modified modified;
+ /**
+ * Callback to create a bitmap with the given dimensions.
+ *
+ * \param[in] width Required bitmap width in pixels.
+ * \param[in] height Required bitmap height in pixels.
+ * \return pointer to client's bitmap structure or NULL on error.
+ */
+ nsgif_bitmap_t* (*create)(int width, int height);
+
+ /**
+ * Callback to free a bitmap.
+ *
+ * \param[in] bitmap The bitmap to destroy.
+ */
+ void (*destroy)(nsgif_bitmap_t *bitmap);
+
+ /**
+ * Get pointer to pixel buffer in a bitmap.
+ *
+ * The pixel buffer must be `width * height * sizeof(uint32_t)`.
+ *
+ * \param[in] bitmap The bitmap.
+ * \return pointer to bitmap's pixel buffer.
+ */
+ uint8_t* (*get_buffer)(nsgif_bitmap_t *bitmap);
+
+ /* The following functions are optional. */
+
+ /**
+ * Set whether a bitmap can be plotted opaque.
+ *
+ * \param[in] bitmap The bitmap.
+ * \param[in] opaque Whether the current frame is opaque.
+ */
+ void (*set_opaque)(nsgif_bitmap_t *bitmap, bool opaque);
+
+ /**
+ * Tests whether a bitmap has an opaque alpha channel.
+ *
+ * \param[in] bitmap The bitmap.
+ * \return true if the bitmap is opaque, false otherwise.
+ */
+ bool (*test_opaque)(nsgif_bitmap_t *bitmap);
+
+ /**
+ * Bitmap modified notification.
+ *
+ * \param[in] bitmap The bitmap.
+ */
+ void (*modified)(nsgif_bitmap_t *bitmap);
} nsgif_bitmap_cb_vt;
/**
@@ -161,8 +196,10 @@ nsgif_result nsgif_frame_prepare(
/**
* Decodes a GIF frame.
*
- * \param[in] gif The nsgif object.
- * \param[in] frame The frame number to decode.
+ * \param[in] gif The nsgif object.
+ * \param[in] frame The frame number to decode.
+ * \param[out] bitmap On success, returns pointer to the client-allocated,
+ * nsgif-owned client bitmap structure.
* \return Error return value.
* - NSGIF_FRAME_DATA_ERROR for GIF frame data error
* - NSGIF_DATA_ERROR for GIF error (invalid frame header)
@@ -173,7 +210,7 @@ nsgif_result nsgif_frame_prepare(
nsgif_result nsgif_frame_decode(
nsgif *gif,
uint32_t frame,
- const uint32_t **buffer);
+ nsgif_bitmap_t **bitmap);
/**
* Reset a GIF animation.
diff --git a/src/gif.c b/src/gif.c
index dd29f0a..5ed38d8 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -67,7 +67,7 @@ struct nsgif {
/** current frame decoded to bitmap */
uint32_t decoded_frame;
/** currently decoded image; stored as bitmap from bitmap_create callback */
- void *frame_image;
+ nsgif_bitmap_t *frame_image;
uint16_t delay_default;
/** number of frames partially decoded */
@@ -1685,13 +1685,13 @@ nsgif_result nsgif_frame_prepare(
nsgif_result nsgif_frame_decode(
nsgif *gif,
uint32_t frame,
- const uint32_t **buffer)
+ nsgif_bitmap_t **bitmap)
{
uint32_t start_frame;
nsgif_result ret = NSGIF_OK;
if (gif->decoded_frame == frame) {
- *buffer = gif->frame_image;
+ *bitmap = gif->frame_image;
return NSGIF_OK;
} else if (gif->decoded_frame >= frame ||
@@ -1710,7 +1710,7 @@ nsgif_result nsgif_frame_decode(
}
}
- *buffer = gif->frame_image;
+ *bitmap = gif->frame_image;
return ret;
}
diff --git a/test/decode_gif.c b/test/decode_gif.c
index 5f5c377..acc2ef0 100644
--- a/test/decode_gif.c
+++ b/test/decode_gif.c
@@ -122,7 +122,7 @@ static void decode(FILE* fh, const char *name, nsgif *gif, bool write_ppm)
/* decode the frames */
while (true) {
- const uint32_t *buffer;
+ nsgif_bitmap_t *buffer;
const uint8_t *image;
uint32_t frame_new;
uint32_t delay_cs;
commitdiff http://git.netsurf-browser.org/libnsgif.git/commit/?id=3fe60b931a6529a019...
commit 3fe60b931a6529a0196d3bf6374a3569c3e7daba
Author: Michael Drake <tlsa(a)netsurf-browser.org>
Commit: Michael Drake <tlsa(a)netsurf-browser.org>
GIF: Don't need previous frame size now.
The image dimensions are constant now.
diff --git a/src/gif.c b/src/gif.c
index 1656db8..dd29f0a 100644
--- a/src/gif.c
+++ b/src/gif.c
@@ -101,10 +101,6 @@ struct nsgif {
void *prev_frame;
/** previous frame index */
uint32_t prev_index;
- /** previous frame width */
- uint32_t prev_width;
- /** previous frame height */
- uint32_t prev_height;
};
/**
@@ -267,7 +263,6 @@ static void nsgif__record_frame(
struct nsgif *gif,
const uint32_t *bitmap)
{
- bool need_alloc = gif->prev_frame == NULL;
uint32_t *prev_frame;
if (gif->decoded_frame == NSGIF_FRAME_INVALID ||
@@ -281,12 +276,7 @@ static void nsgif__record_frame(
return;
}
- if (gif->prev_frame != NULL &&
- gif->info.width * gif->info.height > gif->prev_width * gif->prev_height) {
- need_alloc = true;
- }
-
- if (need_alloc) {
+ if (gif->prev_frame == NULL) {
prev_frame = realloc(gif->prev_frame,
gif->info.width * gif->info.height * 4);
if (prev_frame == NULL) {
@@ -299,8 +289,6 @@ static void nsgif__record_frame(
memcpy(prev_frame, bitmap, gif->info.width * gif->info.height * 4);
gif->prev_frame = prev_frame;
- gif->prev_width = gif->info.width;
- gif->prev_height = gif->info.height;
gif->prev_index = gif->decoded_frame;
}
@@ -309,19 +297,14 @@ static nsgif_result nsgif__recover_frame(
uint32_t *bitmap)
{
const uint32_t *prev_frame = gif->prev_frame;
- unsigned height = gif->info.height < gif->prev_height ? gif->info.height : gif->prev_height;
- unsigned width = gif->info.width < gif->prev_width ? gif->info.width : gif->prev_width;
+ unsigned height = gif->info.height;
+ unsigned width = gif->info.width;
if (prev_frame == NULL) {
return NSGIF_FRAME_DATA_ERROR;
}
- for (unsigned y = 0; y < height; y++) {
- memcpy(bitmap, prev_frame, width * 4);
-
- bitmap += gif->info.width;
- prev_frame += gif->prev_width;
- }
+ memcpy(bitmap, prev_frame, height * width * sizeof(*bitmap));
return NSGIF_OK;
}
-----------------------------------------------------------------------
Summary of changes:
--
NetSurf GIF Decoder
1 year, 7 months