Gitweb links:

...log 
http://git.netsurf-browser.org/libnsgif.git/shortlog/41d8bcf828af44c775454e46ce751f9ac1a3684e
...commit 
http://git.netsurf-browser.org/libnsgif.git/commit/41d8bcf828af44c775454e46ce751f9ac1a3684e
...tree 
http://git.netsurf-browser.org/libnsgif.git/tree/41d8bcf828af44c775454e46ce751f9ac1a3684e

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=41d8bcf828af44c775454e46ce751f9ac1a3684e
commit 41d8bcf828af44c775454e46ce751f9ac1a3684e
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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=7ee51c0f6025238640cfa55faa36cd73e12489c7
commit 7ee51c0f6025238640cfa55faa36cd73e12489c7
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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=a0025eda79e5f6b7f0ae23e7a85fd947dc847726
commit a0025eda79e5f6b7f0ae23e7a85fd947dc847726
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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.wil...@netsurf-browser.org>
  * Copyright 2008 Sean Fox <dynt...@gmail.com>
- * Copyright 2013-2021 Michael Drake <t...@netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <t...@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.wil...@netsurf-browser.org>
  * Copyright 2008 Sean Fox <dynt...@gmail.com>
- * Copyright 2013-2021 Michael Drake <t...@netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <t...@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 <dynt...@gmail.com>
  * Copyright 2008 James Bursa <ja...@netsurf-browser.org>
+ * Copyright 2022 Michael Drake <t...@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=bc358088b2b074f09e638f101d2a12b3632a0d73
commit bc358088b2b074f09e638f101d2a12b3632a0d73
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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=4360a768898cc7a43715926e2e8040cb9748bead
commit 4360a768898cc7a43715926e2e8040cb9748bead
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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=c800fec625521c9cb791d69933c8084e390c2bd0
commit c800fec625521c9cb791d69933c8084e390c2bd0
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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=ba2037410035d5b95e7458576654f8122ec581ac
commit ba2037410035d5b95e7458576654f8122ec581ac
Author: Michael Drake <t...@netsurf-browser.org>
Commit: Michael Drake <t...@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 <t...@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 <t...@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.wil...@netsurf-browser.org>
  * Copyright 2008 Sean Fox <dynt...@gmail.com>
- * Copyright 2013-2021 Michael Drake <t...@netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <t...@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.wil...@netsurf-browser.org>
  * Copyright 2008 Sean Fox <dynt...@gmail.com>
- * Copyright 2013-2021 Michael Drake <t...@netsurf-browser.org>
+ * Copyright 2013-2022 Michael Drake <t...@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 <dynt...@gmail.com>
  * Copyright 2008 James Bursa <ja...@netsurf-browser.org>
+ * Copyright 2022 Michael Drake <t...@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
_______________________________________________
netsurf-commits mailing list -- netsurf-commits@netsurf-browser.org
To unsubscribe send an email to netsurf-commits-le...@netsurf-browser.org

Reply via email to